The Grain Docs

Official manual of the modern web staple 🌾

Bindings

GitHub   Edit on GitHub

On the Subject of let and let rec

The let and let rec statements introduce new names into the current scope.
In other words, they let you declare new bindings (quite similar to constants in other languages).

Syntax

1
2
let name1 = value1 [, name2 = value2]
let rec name1 = func1 [and name2 = func2]

Parameters

name1, name2, …, nameN
Names to be used for the binding identifiers.

value1, value2, …, valueN
Any valid Grain expression.

func1, func2, …, funcN
Any valid Grain functions.

Description

let allows you to introduce names, and let rec allows you to introduce recursive definitions.

Using let

Using let is pretty simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Assigning the value 5 to the name 'foo'
let foo = 5

# Assigning the value of name 'foo' to name 'bar'
let bar = foo

# Assigning this function to the name 'add2'
let add2 = (a, b) => a + b

# Assigning the result of this block to the name 'baz'
let baz = {
let a = 5;
let b = 6;
a * b
} # baz is now the value 30

let is scoped to the nearest block. That really just means it’s scoped to the nearest enclosing curly braces:

1
2
3
4
5
6
7
8
9
10
11
let a = {
# This is allowed, since this 'a' only exists within the enclosing braces.
let a = 3;

let b = 4;
a + b
}

a # This evaluates to 7.

b # This is a name error, since 'b' only existed in the earlier block.

Using let rec

let rec primarily allows you to define recursive functions.
The name of the identifier used will be within the scope of the body of the function:

1
2
3
4
5
6
7
let rec fib = (n) => {
if (n < 2) {
n
} else {
fib(n-1) + fib(n-2)
}
}

let rec also allows you to define mutually recursive functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let rec isEven = (n) => {
if (n > 1) {
isOdd(n-1)
} else {
n == 0
}
}
and isOdd = (n) => {
if (n > 1) {
isEven(n-1)
} else {
n == 1
}
}