Functions and partial application¶
Functions¶
Functions are the most important part of any functional language.
In Arza function syntax somewhat similar to ML but has distinctly Erlangish attributes. Main difference from Erlang is that in Arza arity is not part of the function definition. So you can’t create functions with same name and different arity. This is conscious choice in language design. For example instead of defining three functions for ranges
fun range(to)
fun range(from, to)
fun range(from, to, by)
Better to name different processes differently
fun range(to)
fun range_from(from, to)
fun range_by(from, to, by)
If functions with variadic arity are wanted one can use variadic arguments
fun range(...args) =
match args
| (to) = // code here
| (from, to) = // code here
| (from, to, by) = // code here
Function in Arza can be viewed as match
operator applied to tuple of arguments
The same as with match
for fun
expression in clauses, arguments are sequentially matched
against patterns. If a match succeeds and the optional guard is true, the corresponding body is evaluated.
If there is no matching pattern with a true guard sequence, runtime error occurs.
There are three different types of fun
expression
Simple function¶
This is function with only one clause and optional guard
fun any(p, l) = disjunction(map(p, l))
fun all(p, l) =
conjunction(map(p, l))
fun print_2_if_greater(val1, val2) when val1 > val2 =
io:print("first", val1)
io:print("second", val2)
Case function¶
This is function with multiple clauses
fun foldl
| (f, acc, []) = acc
| (f, acc, hd::tl) = foldl(f, f(hd, acc), tl)
// if after | token there are only one argument and it is not tuple enclosing parentheses might be omitted
fun to_str
| 0 = "zero"
| 1 = "one"
| 2 = "two"
// tuples must be enclosed anyway
| (()) = "empty tuple"
Two level function¶
This is function that combines syntax of previous two. It is a syntactic sugar for common problem of saving first state in deeply recursive processes and also for performing some checks only once
Consider, for example this problem
// this function creates inner function and applies it to all it's arguments
// because it does not want to check all types every iteration and also
// it saves coll from first call
fun scanl(func of Function, accumulator of Seq, coll of Seq) =
fun _scanl
| (f, acc, []) = acc :: empty(coll) // coll contains initial value from first call
| (f, acc, hd::tl) = acc :: scanl(f, f(hd, acc), tl)
in _scanl(func, accumulator, coll)
//In Arza there is special syntax for such operation
fun scanl(func, accumulator, coll)
| (f, acc, []) = acc :: empty(coll)
| (f, acc, hd::tl) = acc :: scanl(f, f(hd, acc), tl)
// it is compiled to
fun scanl(func, accumulator, coll) =
let
fun scanl
| (f, acc, []) = acc :: empty(coll) // coll contains initial value from first call
| (f, acc, hd::tl) = acc :: scanl(f, f(hd, acc), tl)
in scanl(func, accumulator, coll)
// so when recursion calls scanl it will calls inner function not outer
Some function examples
fun count
| 1 = #one
| 2 = #two
| 3 = #three
| 4 = #four
fun f_c2
| (a of Bool, b of String, c) = #first
| (a of Bool, b, c) = #second
| (a, "value", #value) = #third
fun f_c3
| (0, 1, c) when c < 0 = #first
| (a of Bool, b of String, c) = #second
| (a of Bool, b, c) when b + c == 40 = #third
fun map(f, coll)
| (f, []) = empty(coll)
| (f, hd::tl) = f(hd) :: map(f, tl)
Partial application¶
Arza has special syntax for partial application
// underscores here called holes
let add_2 = add(_, 2)
5 = add_2(3)
let sub_from_10 = sub(10, _)
5 = sub_from_10(5)
// you can use more than one hole
let foldempty = foldl(_, [], _)
Also there is builtin function curry which receives normal function and returns carried version
carried_add = curry(add)
3 = carried_add(1)(2)
// in prelude there are two operators
//prefix
fun ~ (func) = curry(func)
3 = ~add(1)(2)
//infix
fun .. (f, g) = curry(f)(g)
3 = add .. 1 .. 2
Because all data immutable in Arza, partial application and currying combined with pipe and composition operators is often the best way to initialize complex data structures or perform chain of operations.
//from prelude
infixl (<|, <|, 15)
infixl (|>, |>, 20)
infixl (<<, <<, 25)
infixl (>>, >>, 25)
fun |>(x, f) = f(x)
fun <|(f, x) = f(x)
fun >>(f, g) = x -> g(f(x))
fun <<(f, g) = x -> f(g(x))
fun twice(f) = f >> f
fun flip(f) = (x, y) -> f(y, x)
//now we can do
let
l = list:range(0, 10)
in
affirm:is_equal (
l |> seq:filter(_, even),
[0, 2, 4, 6, 8]
)
affirm:is_equal(
l |> flip(seq:filter) .. even
|> flip(seq:map) .. (`+` .. 1),
[1, 3, 5, 7, 9]
)
affirm:is_equal (
l |> seq:filter(_, even)
|> seq:map(_, `+` .. 1)
|> seq:map(_, flip(`-`) .. 2),
[-1, 1, 3, 5, 7]
)
affirm:is_equal(
l |> flip(seq:filter) .. (even)
|> flip(seq:map) .. (`+` .. 1)
|> flip(seq:map) .. (flip(`-`) .. 2),
[-1, 1, 3, 5, 7]
)
affirm:is_equal(
l |> seq:filter(_, even)
|> seq:map(_, `+`(1, _))
|> seq:map(_, ~(flip(`-`))(2)(_)),
[-1, 1, 3, 5, 7]
)
let
square = (x -> x * x)
triple = `*` .. 3
in
affirm:is_equal (
l |> seq:filter(_, even)
|> seq:map(_, `+` .. 1)
|> seq:map(_, flip .. `-` .. 2)
|> seq:map(_, triple >> square),
[9, 9, 81, 225, 441]
)
affirm:is_equal (
(seq:filter(_, even)
>> seq:map(_, `+`(1, _))
>> seq:map(_, flip(`-`)(2, _))
>> seq:map(_, triple >> square))(l),
[9, 9, 81, 225, 441]
)
affirm:is_equal (
l |> seq:filter(_, even)
>> ~(flip(seq:map))(`+` .. 1)
>> seq:map(_, flip(`-`)(2, _))
>> ~(flip(seq:map))(triple >> square),
[9, 9, 81, 225, 441]
)