Support for immutability and purity
Reason isn't just about having a static type system. Also important is the fact that Reason is immutable by default. Immutability is an important concept in functional programming. In practice, using immutable data structures (data structures that can't change) results in safer, easier-to-reason-about, and more maintainable code than their mutable counterparts. This will be a recurring theme throughout this book.
Purity is another important concept in functional programming. A function is said to be pure if its output is determined only by its input, without observable side-effects. In other words, a pure function doesn't do anything outside of returning a value. The following is an example of a pure function:
let add = (a, b) => a + b;
And, this is an example of an impure function:
let add = (a, b) => {
Js.log("side-effect");
a + b;
};
The side-effect in this case is writing to the browser's console. That's why, in our preceding Hello World example, BuckleScript included the /* Not a pure module */ comment at the end of the compiled output.
Mutating a global variable is also a side-effect. Consider the following JavaScript:
var globalObject = {total: 0};
const addAndMutate = (a, b) => globalObject.total = a + b;
addAndMutate(40, 2);
/* globalObject now is mutated */
The global object was mutated, and now its total property is 42. We now have to be aware of all areas where this globalObject is mutated whenever using it. Forgetting that this object is both global and mutable can lead to hard-to-debug problems. One idiomatic solution to this problem is to move globalObject into a module where it's no longer global. This way, only that module has access to it. However, we'd still need to be aware of all areas within this module that can update the object.
If globalObject was immutable instead, there would be no way to mutate it. Therefore, we wouldn't need an awareness of all the areas that can mutate globalObject, since there wouldn't be any of these areas. We'll see that, with Reason, it's fairly simple and natural to build real applications in this way by creating updated copies of the original data. Consider the following:
let foo = 42;
let foo = foo + 1;
Js.log(foo);
/* 43 */
The syntax feels quite natural. As we'll see later in this book, immutability—changing by returning updated copies instead of applying destructive changes in place—fits the React/Redux way of doing things quite well.
The original foo was not mutated; it was shadowed. Once shadowed, the old foo binding is unavailable. Bindings can be shadowed in local scopes as well as global scopes:
let foo = 42;
{
let foo = 43;
Js.log(foo); /* 43 */
};
Js.log(foo); /* 42 */
let foo = 43;
Js.log(foo); /* 43 */
Trying to mutate foo results in a compilation error:
let foo = 42;
foo = 43;
/* compilation error */
We can see that immutability and purity are related topics. Having a language that supports immutability allows you to program in a pure way without side-effects. However, what if there are times when purity would cause the code to become more complex and harder to reason about than using side-effects? You may be relieved to learn that Reason (interchangeable with OCaml throughout the rest of this book) is a pragmatic language that let's us cause side-effects when needed.
It's also important to know that immutability doesn't come at the cost of performance. Under the hood, there are optimizations in place that keeps Reason's immutable data structures fast.