Build a ReasonML Calculator App: Part III
Final steps

In this final part of the tutorial, we will compute the result of an expression. Remember that an expression is modelled as a list of things of type symbol. A symbol can represent an arithmetic operation, a digit, or a dot. This data type categorisation was massively helpful in Part II where the build up of an expression had to to go through scrutinisation rules that satisfy the calculator specs.
Similarly, the logic, that will be used to calculate the result, will benefit greatly from the data structure we built in Part II. The final version of our app can be inspected by checking out to the master branch of the accompanying repository.
Section 4: Compute The Result of an Expression
An example of a sanitised expression of symbols is:
[Two, Multiply, Eight, Four, Dot, Three, Add, Four, Dot, Five, Multiply, Nine, Nine]This expression evaluates to 541.56
Remember that symbols are appended to the beginning of the expression list. Therefore, the expression can be constructed by reading symbols from right to left: 99 x 5.4 + 3.48 x 2.
How can the ReasonML’s expression list be translated to its corresponding mathematical representation? Or can the expression be evaluated without directly building the mathematical equivalence?
First of all, note that in order to carry out any mathematical operation in the expression list, the digits must be grouped to form numbers:

Then, operations can be applied according to operator precedence; i.e. multiplication/division precede addition/subtraction:

Finally, we apply addition/subtraction:

OK. That was easy! But how can we accomplish this in ReasonML? As usual, there are a number of ways. For instance, arrays can be used to store elements of different types; hence, a single array can hold the intermediate results and operators. However, let’s try to constrain ourselves to lists where the type of all elements must be the same. Consequently, numbers and operators must be kept in separate lists. For example, assuming digits were already grouped into numbers, the symbols list can be represented by two lists:
listOfNumbers = [2, 3.48, 5.4, 99]
listOfOperators = [Multiply, Add, Multiply] listOfOperators is already of type symbol list. Thus, it can be easily constructed by filtering the expression list:
In order to create listOfNumbers, digits must be first grouped into numbers.
Grouping Digits
Similar to the last snippet, the process of constructing listOfNumbers starts with filtering out arithmetic operations. However, since the type of elements in the new list will be of type float, List.filter cannot be used. Instead, List.fold_right will be used.
Note that:
- The accumulator holds elements of
stringtype. - As the list is traversed from left to right, new digits are converted to a string and then appended to the currently constructed number.
- Numbers are separated using arithmetic operations.
Multiplication & Division
Given listOfNumbers and listOfOperators, the order of elements in both lists is important in order to retain the integrity of the original expression. Multiplication and division is then applied to the elements of listOfNumbers based on the element’s order. We can use the fold_right2 function to traverse the two lists simultaneously. However, fold_right2 requires that the two lists it takes as argument be of the same length. listOfOperatos can be extended by one element that is not a multiplication or a division. Thus, we add a dummy Add symbol to the end of listOfOperators.
Then multiplication and division is applied to listOfNumbers:
Note that we had to use *. and /. to multiply and divide float numbers. This is something that ReasonML inherited from OCaml.
Addition and Subtraction
The final result can be computed by exhausting addition and subtraction:
Finally, we stitch everything together and we have a ReasonML calculator. To see the final version of the code, checkout to the master branch on the accompanying repository.
Conclusion
We were able build an expression-based calculator using ReasonML. We exploited the type system offered by ReasonML to build a model for the calculator app. This model was represented by a list of symbols. Pattern matching was then used to transform the model, calculate intermediate results, and subsequently compute the final result.
Before you depart this tutorial, let’s ask ourselves a couple of questions:
Is the solution offered by ReasonML over-complicated?
Let’s see how this can be done in JavaScript:
The state of a ReactJS component can be as simple as a string such as "9 + 9.9" .
Now, because JavaScript is awesome, evaluating this expression is as easy as doing: eval("9 + 9.9") . Job done!
Note that we ignored expression sanitisation here.
Therefore, the answer to the above question is: it depends! While it is easier to get all this work done in JavaScript, ReasonML’s type system and pattern matching makes catching bugs and refactoring much easier than JavaScript. Also, one can argue that once a developer is comfortable with ReasonML, tackling complicated problems by modelling the solution using the type system might be faster and less error-prone than JavaScript.
What about other languages such as OCaml and Elm?
If you have checked other projects in my Gitlab profile, you’d have noticed that there are implementations for the calculator app using OCaml and Elm. I also tried to use some frontend frameworks in PureScript and Haskell but the developer toolings for both of these languages were disappointing. However, I read that the communities for those two languages are working hard to improve the developer experience.
The main difference between the implementations for OCaml/Elm and that of ReasonML is the architecture. In OCaml and Elm, The Elm Architecture (TEA) is used instead of React.
What follows is a highly opinionated comparison:
Based on my limited experience:
- The TEA architecture is superior to React.
- I loved Elm’s concise syntax and explicit function signatures.
- OCaml offers the best compromise between concise syntax and pragmatism (thanks to BuckleScript’s ecosystem.)
- Experience with tooling is very similar. Both BuckleScript and Elm compilers are extremely good and fast.
- Elm has a smaller footprint in
node_modulesthan BuckleScript.
Please leave a comment if you spot a spelling mistake or an error in the code snippet. All feedback is welcome.
