Let declarations and functions
Let declarations bind identifiers to values or functions.
For instance:
let
a = 1,
f(v: int) = v * 2
in
a + f(1) // Result is 3
This let
declaration binds the identifier a
to the value 1
, and the identifier f
to a function that receives a int parameter and returns its double.
The in
part of the declaration contains an expression. The final value of the let declaration is the value of the expression in the in
(in the example the value is 3). The in
has access to all the identifiers defined in the declaration. In the example above, the in
has access to a
and f
. The identifier f
, however, only has access to the identifier a
, since in the example that is the only identifier defined before itself.
There are three types of declarations in the let
part:
- assign an identifier to a value, e.g.
a = 1
. The identifier can optionally be typed as ina: int = 1
. - assign an identifier to a function, e.g.
f(v: int) = v * 2
. The return type of the function can optionally be typed as inf(v: int): int = v * 2
- assign an identifier to a recursive function, e.g.
rec fact(v: int): int = if (v <= 1) then 1 else v * fact(v - 1)
. Recursive functions must be prefixed by the keywordrec
. In addition, recursive functions require the user to specify the return type.
Let declarations create a new scope. This means that within a let declaration you can assign an identifier with the same name as an existing identifier to a new value. This does not overwrite the old identifier: instead it defines a new identifier that only exists in the scope of the new let declaration. For instance:
let
a = 1, // defines an identifier called 'a' that is bound to the value 1
b = // defines an identifier called 'b' whose value is the value of
// the 'in' of its inner let below.
let // a new let declaration, which creates a new scope.
a = a + 1 // a new identifier called 'a' is bound to the value
// of the old identifier called 'a' (value of 1) plus 1
// with the resulting value of 2.
in
a // refers to the new identifier 'a' with value 2.
// therefore, the value of 'b' is 2.
in
a + b // 'a' is an identifier with value 1, 'b' is an identifier with value 2.
// therefore, the result is 3.
Functions
Functions can be defined in let declarations as shown above.
However, it is also possible to define lambda functions, i.e. anonymous functions. These are particularly useful when calling built-in libraries.
For instance, the following expression creates a lambda function that receives an int.
(v: int) -> v + 1
Lamda functions are particularly useful to create functions with more powerful behaviours. For instance, here is a function that says "Hi {name}>!" but first calls a user-defined cleaning function that cleans the name.
let say(name: string, cleaner: (string) -> string) = "Hi " + cleaner(name) + "!"
This could be used as e.g.:
let
say(name: string, cleaner: (string) -> string) = "Hi " + cleaner(name) + "!"
in
say("John", s -> String.Upper(s)) // Result is "Hi JOHN!"
Optional parameters
Functions can have optional parameters, e.g.:
let f(x: int, y: int = 2) = x > y
in f(1) // Same as f(1, 2)