Let expression defines a variable like this:
let name = initializer
next
Example:
let sum = 2 + 3
assert (sum = 5)
When initializer
expression is multiline, indent like this:
let name =
initializer
next
Example:
let sum =
2
+ 3
+ 5
assert (sum = 2 + 3 + 5)
next
expression must be indented on the same column as let
keyword.
// let keyword is indented by 4 spaces here
// v
let x = 2
assert (x = 2)
// ^
// and then next expression must be indented exactly 4 spaces
initializer
expression must be indented deeper than let
.
// let keyword is indented by 4 spaces here
// v
let sum =
2 + 3
// ^ initializer expression must be indented more than 4 spaces
// (so that it is distinct from next expression)
assert (sum = 5)
The type of variable is same as the type of initializer
expression. If you want to make it explicit, write a type ascription like this:
let sum: int = 2 + 3
// ^^^^^
// This states the type of sum is int explicitly.
assert (sum = 5)
Compiler reports a type error if the ascription is wrong.
Remark: Type ascription doesn't change the value of expression. Type ascription is not type casting.
Let expression also defines a function like this:
let name (p1: P1Type) (p2: P2Type): ResultType =
body
next
Example:
let squareSum (x: int) (y: int): int =
x * x + y * y
// 3 * 3 + 4 * 4 = 9 + 16 = 25
assert (squareSum 3 4 = 25)
These type ascriptions are recommended for readability, but optional:
let squareSum x y = x * x + y * y
assert (squareSum 3 4 = 25)
Recursive let expression defines a recursive function.
let rec name param1 param2 ... = body
next
Example:
// Calculates the sum of integers from 0 to n (inclusive).
let rec sum n =
if n <= 0 then
0
else
n + sum (n - 1)
assert (sum 0 = 0)
assert (sum 1 = 1 + 0)
assert (sum 2 = 2 + 1 + 0)
assert (sum 3 = 3 + 2 + 1 + 0)
Function defined by let-fun
is polymorphic as possible.
// Whatever the type of x is, `id x` should pass type checking.
// So the type of `id` is T -> T for any type T.
let id x = x
assert (id 1 = 1)
// ^^ id: int -> int here
assert (id "two" = "two")
// ^^ id: string -> string here
(TODO: Explain more. If you want to learn by yourself, search for Hindley-Milner type inference.)
Let expression has several variants of syntax.
// let-val expression
let pattern = initializer
next
// let-fun expression
let name param1 param2 ... = body
next
// let-rec expression
let rec name param1 param2 ... = body
next
let-val
expression just expands to match expression:
let pattern = initializer
next
// ==>
match initializer with
| pattern -> next
The difference between let-fun
and let-rec
is only at name resolution.
// With rec:
// This denotes to the function itself.
// v
let rec f () = f ()
// Without rec:
let f () = f ()
// ^
// This denotes to the function defined above, not itself.