Blog index
2019-03-28 Cristian Carlesso

Writing purely ReasonML

I’ve recently stumbled upon a series of videos where Tsoding solves some brain teaser from Hacker Rank

but, you know, in Haskell

In the first episode the problem was to get some integers as an input from the stdio and to sum them together.

This one line[1] is the proposed solution:

main 
  = interact 
  $ show 
  . sum 
  . map read 
  . tail 
  . words

We know that OCaml is usually compared to Haskell, and that Reason is basically just OCaml(TM).

Surely it would be easy to use imperative code to solve this problem, but can we reach something as close as possible to the solution above?

One thing that makes the expression above very readable is the chain of function compositions, unfortunately Reason doesn’t provide a . operator, but luckily is super easy to define our own operators.

Unfortunately, we cannot use . as it’s a reserved token, but we can though take inspiration from F#:

F# defines this two operators:

>> Composes two functions (forward composition operator)

<< Composes two functions but in reverse order: it executes the second one first (backward composition operator).

Let’s define the same operators in ReasonML:

First let’s define a compose function:

let compose 
  = (f, g, x) => g(f(x));

Let’s also define a backwardsCompose function that execute the second function first:

let backwardCompose 
  = (f, g, x) => f(g(x));

Now it’s possible to associate the two operators to these functions:

let (>>) = compose;
let (<<) = backwardCompose;

After this, let’s substitute Haskell . for << and drop the main = part as Reason doesn’t need it:

interact 
  $ show 
  << sum 
  << map read 
  << tail 
  << words

We’re unfortunately not done yet as those functions don’t exist in Reason, so we must define them ourself.

Let’s temporarily ignore interact for a moment and try to define the remaining functions:

let show = string_of_int;
let sum = List.fold_left((+), 0);
let map = List.map;
let tail = List.tl;
let read = int_of_string;
let words 
  = Str.split 
  @@ Str.regexp("[ \t\n]+");

Notice how here I’ve used the @@ application operator:

a @@ b is equivalent to a(b).

This is the equivalent to $ in Haskell, so we can either change the $ to @@ in the original expression or, since it’s not defined yet, try to alias $ to @@:

let ($) = @@;

We’re only left with interact.

interact puts all the standard input in a string; it then executes a function with it redirecting the output to the standard output.

I couldn’t find anything similar in ReasonML, so I hacked together the interact function using read_line instead:

let interact 
  = f => {
    let acc = ref([]);
    try (
      while (true) {
        acc := List.concat(
          [
            acc^, 
            [read_line()]
          ]
        );
      }
    ) {
    | End_of_file => 
      String.concat("\n", acc^) 
      |> f 
      |> print_string
    };
  };

Ugh... this uses mutations and imperative code and handling with I/O.

Last thing remaining yet to do is to change the application of map read as I’ve describe above in Reason function application is not the space character:

interact 
  $ show 
  << sum 
  << map(read) 
  << tail 
  << words

Rewriting this by reversing the functional composition gives us a more top-down flow:

interact 
  $ words 
  >> tail 
  >> map(read) 
  >> sum 
  >> show;

if we try to compile this, we’ll see that it doesn’t quite work as expected due of how Reason infer the associativity of $ and >>, and it‘s solved by adding parenthesis:

interact 
  $ (
    words 
    >> tail 
    >> map(read) 
    >> sum 
    >> show
  );

The main reason $ exists in Haskell is because of associativity though.

Another way to avoid using parentheses we have in Reason is to use the reverse application operator |> and moving interact to the end:

words 
  >> tail 
  >> map(read) 
  >> sum 
  >> show 
  |> interact

Conclusions

Apart for the interact function we reproduced a solution very similar to the Haskell one, while keeping the same level of purity using mostly a functional approach.


  1. here I use indentation for people reading on a smartphone. ↩︎