Functions
Functions are Yona’s basic building block. They are first-class values: you can pass them, return them, store them in data structures, and apply them partially.
Definitions
Section titled “Definitions”A function is a name, space-separated parameter patterns, =, and a body.
This is the form the standard library uses (map fn seq = …):
add x y = x + y
add 1 2 # => 3There is no name(x, y) -> body definition syntax. Parentheses around
parameters are a pattern: add (x, y) = x + y is a one-argument function
that matches a tuple, not a two-argument function.
Parameters are patterns, so a definition can have several clauses. Clauses
are tried top to bottom; the first whose patterns match is used. Recursion
is often clearer as a case in one clause — the same shape as Std\List:
factorial n = case n of 0 -> 1 _ -> n * factorial (n - 1)end
factorial 5 # => 120Guards
Section titled “Guards”An optional if guard after the parameters restricts when a clause
applies; if the guard is false, matching falls through to the next
clause:
abs x if x >= 0 = xabs x if x < 0 = -x
abs (-3) # => 3Put more specific clauses first; matching is strictly top-to-bottom.
Type annotations
Section titled “Type annotations”Annotations are optional — the compiler infers every type (see Types and data). When you want one, write a Haskell-style signature on the line before the definition:
scale : Float -> Float -> Floatscale factor x = factor * x
greet : String -> Stringgreet name = "Hello " ++ name
greet "Yona" # => "Hello Yona"Arrows in the signature are curried: Float -> Float -> Float is a
function of one Float returning a function of one Float.
Lambdas and thunks
Section titled “Lambdas and thunks”Anonymous functions use a backslash:
\x -> x * 2\(x, y) -> x + y # tuple-pattern parameterA thunk is a zero-parameter lambda, written with no parameters at all:
\-> expensiveComputationZero-arity functions auto-evaluate
Section titled “Zero-arity functions auto-evaluate”Because evaluation is strict, referencing a zero-arity function by name calls it. To pass a zero-arity function as a value without calling it, wrap it in a thunk:
let getTime = \-> System.nanoTime inlet t = getTime in # calls it — t is a numberlet deferred = \-> getTime inrunLater deferred # passes the function, does not call itApplication
Section titled “Application”Juxtaposition
Section titled “Juxtaposition”The primary application syntax is juxtaposition — the function followed by space-separated arguments, as in Haskell or ML:
add 1 2 # => 3map (\x -> x * 2) [1, 2, 3] # => [2, 4, 6]Application binds tighter than every binary operator, so f x + g y is
(f x) + (g y). Parenthesize an argument when it is itself an application
or contains operators: f (g x), add (1 + 2) 3.
f(x) is the same as f x. f(x, y) is not a two-argument call — it
applies f to the tuple (x, y). For add x y = x + y, add 1 2 is 3
and add(1, 2) is a leftover function.
Partial application and currying
Section titled “Partial application and currying”Applying a function to fewer arguments than it takes returns a function of the remaining arguments:
let add5 = add 5 inadd5 10 # => 15Functions that return functions chain naturally:
let adder n = \x -> x + n inadder 10 32 # => 42
let f a = \b -> \c -> a + b + c inf 1 2 3 # => 6Closures
Section titled “Closures”A function captures the free variables of its enclosing scope by value at the point of definition. The captured environment travels with the function, including through higher-order calls:
let n = 10, addN = \x -> x + n, # addN captures n apply = \f x -> f x inapply addN 5 # => 15Implementation note. Closures compile to a heap record holding the function pointer and the captured values; recursive closures use a weak self-reference so a closure that mentions itself does not leak.
|> feeds a value into a function left to right; <| is the same, right
to left. Pipes have the lowest precedence, so the whole expression on each
side is evaluated first:
import map, filter, sum from Std\List in[1, 2, 3, 4, 5] |> filter (\x -> x % 2 == 1) |> map (\x -> x * x) |> sum # => 35
sum <| map (\x -> x * x) <| [1, 2, 3] # => 14Use |> for data-transformation pipelines — the value flows visibly
through each stage.
Higher-order functions
Section titled “Higher-order functions”Functions take and return functions freely. The stdlib and prelude are
built on this: map, filter, fold in Std\List, and
prelude combinators that need no import:
identity 42 # => 42const 1 "ignored" # => 1flip (\a b -> a - b) 1 10 # => 9compose (\x -> x + 1) (\x -> x * 2) 5 # => 11 (applies g, then f)import foldl from Std\List infoldl (\acc x -> acc + x) 0 [1, 2, 3, 4] # => 10Std\List.foldl is the idiomatic aggregation loop — it is tail-recursive
and never overflows the stack, unlike a hand-written right recursion over a
long sequence.
Writing your own higher-order function is nothing special:
twice f x = f (f x)
twice (\x -> x * 3) 2 # => 18Recursion
Section titled “Recursion”There is no loop syntax; iteration is recursion (or a generator / stdlib function that encapsulates it). Multiple clauses plus guards make recursive definitions read like their mathematical specification:
fib n = case n of 0 -> 0 1 -> 1 _ -> fib (n - 1) + fib (n - 2)end
fib 10 # => 55For sequence recursion, pattern-match on head and tail — see Pattern matching:
sum xs = case xs of [] -> 0 [h|t] -> h + sum tend
sum [1, 2, 3, 4, 5] # => 15Where to next
Section titled “Where to next”- Pattern matching — the pattern forms usable in
parameters and
case. - Types and data — how the checker infers function types.
- Collections — the functions in
Std\Listand friends.