1st March 2019 • 5 min read
A basic introduction to functional programming principles in ReasonML
Seif Ghezala
Functional programming is a programming paradigm based on several principles such as immutability and purity.
ReasonML extends the functional language OCaml and compiles to JavaScript.
This article aims to introduce you to some very basic functional principles present in ReasonML
and OCaml
. We will see how these principles are closely related and help avoid errors.
What is a variable?
A variable is an identifier bound to a specific value.
1// name is a variable bound to the value "John"
2let name = "John";
Mutable variables
In most programming languages, a variable can be changed to identify another value after its initial declaration:
1// initial declaration: name identifies "John"
2let name = "John";
3
4// mutation: name identifies "Marie" now
5name = "Marie";
When this happens, we say that the variable is mutated. Therefore, by design, the variable name is mutable.
Immutable variables in Reason
Reason approaches variable bindings differently. In fact, a variable always identifies the same value assigned to it during declaration. This prevents variables from being mutated, making most variables in Reason immutable:
1// initial declaration: name identifies "John"
2let name = "John";
3
4// Error
5name = "Marie";
Note: not all variable types in Reason are immutable. Some variables such as arrays and objects can actually be mutated.
Pure and impure functions
A pure function is one that doesn’t produce any effects on its surrounding state.
In multi-paradigms programming languages such as JavaScript, it’s possible to create both pure and impure functions. This makes it possible to achieve the same results through completely different programming styles.
Let’s create a function that counts the number of occurrences of a certain word in a given array:
1// example 1: impure function
2let count = 0;
3function countWordOccurences(arr, word) {
4 for (let value in arr) {
5 if(value === word) {
6 count++;
7 }
8 }
9}
10
11// example 2: pure function
12function countWordOccurences(arr, word) {
13 arr.reduce((count, value) => {
14 return value === word ? count + 1 : count;
15 }, 0):
16}
Both examples achieve the same result.
However, the function in the first example is impure because it alters the value of count , which is in its surrounding state. This is what we call a side effect.
ReasonML is not a pure functional programming language. This means that you can still write impure functions that produce side effects.
However, the immutable bindings we saw earlier will enforce you most of the time to write pure functions. Since the count binding can’t change, you can’t alter its value and reproduce the code of the first example.
When functions are first-class citizens, they are considered just like values. They can be passed around as arguments. If you’re familiar with JavaScript, this concept is probably not surprising for you.
Why is this is useful? Simply because it opens the door to some interesting language features.
Higher-order functions
A higher-order function is one that takes a function as an argument or returns one:
1let numbers = [1, 2, 3, 4];
2
3let multiplyBy2 = x => x * 2;
4
5let numbersX2 = List.map(multiplyBy2, numbers);
List.map is a higher-order function. It takes two arguments: a function and a list and returns a list which is the result of applying the input function of the entries of the input list:
1let getMultiplierByN = n => x => n * x;
2
3let multiplierBy2 = getMultiplierByN(2);
4
5multiplierBy2(3); // output: 6
getMultiplierByN is another higher-order function. It takes an argument n and returns a function that multiplies its argument by n . We used it to create a function that multiples numbers by 2.
Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope. Kyle Simpson, You Don’t Know JS
In fact, we used the concept of closure in the last getMultiplierByN function example. The function returned by getMultiplierByN has access to the value n, which is stored in the scope of its parent function geMultiplierByN.
Higher-order functions + closures = curriable functions.
Let’s look at the following example:
1// function add: takes 2 arguments: x and y and returns their sum
2let add = (x, y) => x + y;
In Reason, this function is automatically converted to:
1let add = x => y => x + y;
This is the equivalent in JavaScript:
1// function add: takes an argument x and returns
2// a function F
3
4function add(x,y) {
5// function addX: it takes an argument y and returns its sum
6// with the value x stored in the closure of add
7
8 function addX(y) {
9 return x + y;
10 }
11}
This allows us to partially call the function:
1let add5 = add(5);
2add5(4); // output: 9
3
4let addSeven = add(7);
5addSeven(3); // output: 10
This style of functions is called: a curried function. Translating a function that takes multiple arguments into a sequence of functions that can a single argument is called currying.
In Reason, currying comes out of the box without needing to declare functions differently.