Functions are first-class citizens in Elara. Every function takes exactly one argument and returns one value, with multi-argument functions achieved through currying.
Function definitions
Functions are defined with a type signature (def) followed by an implementation (let):
def add : Int -> Int -> Int
let add x y = x + y
def greeting : String -> String
let greeting name = "Hello, " ++ name
The def keyword declares the type, while let provides the implementation. Both are required for top-level functions.
Function application
Functions are applied using juxtaposition (space-separated):
def add : Int -> Int -> Int
let add x y = x + y
let main = println (add 2 3) -- Prints 5
Parentheses control application order:
def result1 : Int
let result1 = add 2 (add 3 4) -- 2 + (3 + 4) = 9
def result2 : Int
let result2 = add (add 2 3) 4 -- (2 + 3) + 4 = 9
Lambda expressions
Anonymous functions use backslash \ syntax:
def double : Int -> Int
let double = \x -> x * 2
def add : Int -> Int -> Int
let add = \x -> \y -> x + y
let main = println (map (\x -> x * x) [1, 2, 3])
From the real examples:
def map : (a -> b) -> List a -> List b
let map f list =
match list with
Nil -> Nil
Cons x xs -> Cons (f x) (map f xs)
let main =
let numbers = [1, 2, 3, 4, 5]
let squared = map (\x -> x * x) numbers
println (listToString squared)
Currying and partial application
All functions in Elara are automatically curried. Multi-argument functions are actually single-argument functions returning functions:
def add : Int -> Int -> Int
let add x y = x + y
-- Partial application
let addFive : Int -> Int
let addFive = add 5
let result : Int
let result = addFive 10 -- 15
Real-world example:
def multiply : Int -> Int -> Int
let multiply x y = x * y
let main =
-- Partial application
let addFive = add 5
let double = multiply 2
-- Function chaining
let result = addFive (double 10)
println ("(10 * 2) + 5 = " ++ toString result) -- 25
The type Int -> Int -> Int is actually Int -> (Int -> Int), meaning a function that takes an Int and returns another function.
Higher-order functions
Functions can accept other functions as arguments or return them as results:
def apply : (a -> b) -> a -> b
let apply f x = f x
def compose : (b -> c) -> (a -> b) -> (a -> c)
let compose g f = \x -> g (f x)
Examples from the standard library:
def map : (a -> b) -> List a -> List b
let map f list =
match list with
Nil -> Nil
Cons x xs -> Cons (f x) (map f xs)
def filter : (a -> Bool) -> List a -> List a
let filter p list =
match list with
Nil -> Nil
Cons x xs ->
if p x then
Cons x (filter p xs)
else
filter p xs
Using higher-order functions
let main =
let numbers = [1, 2, 3, 4, 5]
let squared = map (\x -> x * x) numbers
let large = filter (\x -> x > 3) squared
println ("Original: " ++ listToString numbers) >>
println ("Squared: " ++ listToString squared) >>
println ("> 3: " ++ listToString large)
Recursion
Elara supports recursive functions naturally:
def factorial : Int -> Int
let factorial n = if n == 0 then 1 else n * factorial (n - 1)
def fib : Int -> Int
let fib n =
if n <= 1 then n
else fib (n - 1) + fib (n - 2)
Recursion on lists:
def sum : List Int -> Int
let sum ls =
match ls with
[] -> 0
x::xs -> x + sum xs
def length : List a -> Int
let length list =
match list with
Nil -> 0
Cons x xs -> 1 + length xs
Elara does not currently optimize tail recursion. Be mindful of stack depth with deeply recursive functions.
Operators as functions
Infix operators are functions and can be used as such:
-- Using (+) as a prefix function
def add : Int -> Int -> Int
let add = (+)
-- Partially applying an operator
def increment : Int -> Int
let increment = (+) 1
From the standard library:
-- Pipe operator
def (|>) : a -> (a -> b) -> b
let (|>) x f = f x
-- Function composition
def (>>) : (a -> b) -> (b -> c) -> (a -> c)
let (>>) f g = \x -> g (f x)
Function chaining
Use the pipe operator |> for readable function chains:
let main =
let result = 42
|> (\x -> x + 1)
|> (\x -> x * 2)
|> toString
println result -- Prints "86"
Example from source:
let main =
let identity = makeIdentity i
toString (identity + 1) |> println
Local functions
Define functions locally with let ... in expressions:
def calculate : Int -> Int
let calculate n =
let helper x = x * 2
let another y = y + 1
helper (another n)
Real example:
def listToString : List a -> String
let listToString l =
let listToStringGo l_ =
match l_ with
Nil -> ""
Cons x Nil -> toString x
Cons x xs -> toString x ++ ", " ++ listToStringGo xs
match l with
Nil -> "[]"
Cons x xs -> "[" ++ listToStringGo (Cons x xs) ++ "]"