4. Functions¶
A function is a value like any other. It can be bound to a name, passed to a step, returned from another function, and written once for every type it works on.
Lambdas and their types¶
n => n * 2 is a lambda. Written as an argument, it takes its parameter's
type from the step it is passed to. Written on its own, it states the type:
double = (n: Int) => n * 2
[412, 280] | map(double)
[824, 560]
A function's type is written with an arrow: Int -> Int, (Int, Int) -> Int.
double = (n: Int) => n * 2
double
Int -> Int
Piping into a function calls it: x | f is f(x).
double = (n: Int) => n * 2
280 | double
560
Declared functions¶
fn declares a function with typed parameters and a result type. Its name is
bound before its body is checked, so it may call itself:
fn factorial(n: Int) : Int = 1 given n <= 1 otherwise n * factorial(n - 1)
[1, 2, 3, 4, 5] | map(factorial)
[1, 2, 6, 24, 120]
A function may take a function, and return one. The returned function keeps the values it captured:
fn adder(k: Int) : Int -> Int = n => n + k
fn twice(f: Int -> Int, x: Int) : Int = f(f(x))
twice(adder(3), 30)
36
A last parameter written ...T collects any number of trailing arguments into
a list:
fn total(xs: ...Int) : Int = xs | sum
[total(), total(1, 2), total(1, 2, 3, 4)]
[0, 3, 10]
Generic functions¶
A function written once for every type names a type parameter, one capital letter in angle brackets. Each call fills it from its arguments:
fn first<T>(xs: List<T>) : Option<T> = xs | reduce((a, b) => a)
first(["Dune", "Emma"])
"Dune"
The same function over a list of numbers gives an Option<Int>:
fn first<T>(xs: List<T>) : Option<T> = xs | reduce((a, b) => a)
first([412, 280])
Option<Int>
Inside the body T is a type of its own: the body may rely only on what the
parameter promises. T : Orderable promises an order, so the body may sort or
find a minimum, and each call checks that the promise holds:
fn shortest<T : Orderable>(xs: List<T>) : Option<T> = xs | min
shortest([412, 280, 310])
280
fn shortest<T : Orderable>(xs: List<T>) : Option<T> = xs | min
shortest([{1}, {2}])
type error: `shortest` needs T : Orderable, and T is Set<Int> here
--> <expression>:2:1
|
2 | shortest([{1}, {2}])
| ^^^^^^^^^^^^^^^^^^^^
Aliases¶
Most small functions are a pipeline fragment with a name. := defines one, and
the checker writes its type: the input is the most general one the fragment
accepts, and each parameter takes the type of the step argument it fills.
top(n) := sort | take(n)
[412, 280, 310, 145] | top(2)
[145, 280]
hql check prints the signature it wrote:
top(n) := sort | take(n)
1
top : (Collection<T>, Int) -> List<T> where T : Orderable
Int
Holes¶
A call with _ for an argument is a function of the arguments left open — a
partial application:
fn add(a: Int, b: Int) : Int = a + b
[1, 2, 3] | map(add(10, _))
[11, 12, 13]
Absence, once more¶
Option<T> is how a function says a value may be missing. some(x) and
none build one, match takes one apart, and somes keeps the present values
of a collection:
fn rating_of(r: Int) : Option<Int> = some(r) given r > 0 otherwise none
[5, 0, 4] | map(rating_of) | somes
[5, 4]
What is next¶
With functions and collections in hand, chapter 5 asks the questions SQL asks: where, order by, group by, and the aggregates.