Functions
Functions are defined with a name, parameters, =, and a body. All functions
are first-class, automatically curried, and can be used anywhere a value is
expected.
Definition
A function binding lists its parameters before =. The body is a single
expression or an indented scope.
add x y = x + y
result = add 3 4
build = {target = c, os = linux, arch = x86_64}
add = <closure>
result = 7
When the body needs local bindings, indent them under an explicit result expression:
distance x1 y1 x2 y2 = result
dx = (x2 - x1) ** 2
dy = (y2 - y1) ** 2
result = dx + dy
a = distance 0 0 3 4
build = {target = c, os = linux, arch = x86_64}
distance = <closure>
a = 25
Application
Function application is juxtaposition (space-separated), and it is
left-associative: f a b means (f a) b.
add 3 4 -- 7
(add 3) 4 -- same thing
Lambdas
Anonymous functions use \params -> body.
double = \x -> x * 2
add = \x y -> x + y
a = double 5
b = add 3 4
build = {target = c, os = linux, arch = x86_64}
double = <closure>
add = <closure>
a = 10
b = 7
Lambdas are ordinary values and appear frequently as arguments to higher-order functions.
Currying & Partial Application
Every function is automatically curried. Supplying fewer arguments than a function expects returns a new function that waits for the remaining ones.
add x y = x + y
add5 = add 5
result = add5 10
build = {target = c, os = linux, arch = x86_64}
add = <closure>
add5 = <closure>
result = 15
This makes it natural to build specialised functions on the fly:
doubled = map (\x -> x * 2) [1, 2, 3, 4]
evens = filter (\x -> x % 2 == 0) [1, 2, 3, 4, 5, 6]
total = fold (+) 0 [1, 2, 3, 4, 5]
build = {target = c, os = linux, arch = x86_64}
doubled = [2, 4, 6, 8]
evens = [2, 4, 6]
total = 15
Pipes & Composition
The pipe operator |> passes a value as the last argument to a function,
reading left-to-right:
result = [1, 2, 3, 4, 5] \
|> map (\x -> x * 2) \
|> filter (\x -> x > 4) \
|> sum
build = {target = c, os = linux, arch = x86_64}
result = 24
Composition operators combine functions without naming an intermediate value.
>> composes left-to-right and << composes right-to-left:
double x = x * 2
inc x = x + 1
double_then_inc = double >> inc
inc_then_double = inc >> double
a = double_then_inc 5
b = inc_then_double 5
build = {target = c, os = linux, arch = x86_64}
double = <closure>
inc = <closure>
double_then_inc = <closure>
inc_then_double = <closure>
a = 11
b = 12
Recursion & Tail-Call Optimisation
Functions can reference themselves by name. Milang detects self-calls (and
mutual calls) in tail position and compiles them with goto-based trampolining,
so they run in constant stack space.
factorial n = if (n == 0) 1 (n * factorial (n - 1))
result = factorial 10
build = {target = c, os = linux, arch = x86_64}
factorial = <closure>
result = 3628800
A tail-recursive accumulator style avoids building up a chain of multiplications:
fac_acc acc n = if (n == 0) acc (fac_acc (acc * n) (n - 1))
result = fac_acc 1 20
build = {target = c, os = linux, arch = x86_64}
fac_acc = <closure>
result = 2432902008176640000
Higher-Order Functions
A higher-order function accepts or returns another function.
twice f x = f (f x)
inc x = x + 1
a = twice inc 3
b = twice (\x -> x * 2) 3
build = {target = c, os = linux, arch = x86_64}
twice = <closure>
inc = <closure>
a = 5
b = 12
if Is a Function
Milang has zero keywords. if is an ordinary user-defined function in the
prelude. It uses auto-quote parameters (#param) so the compiler
automatically delays evaluation of each branch — only the chosen one runs:
if (x > 0) "positive" "non-positive"
No special syntax is needed at the call site. The if definition uses #t
and #e parameters which trigger automatic quoting; inside the body, $t and
$e splice (evaluate) only the selected branch. See the
Metaprogramming chapter for details on auto-quote params,
and Thunks & Laziness for the older ~ approach.