Blog indexI’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:
show in Haskell converts from a number to a string, given that this problem is the sum of integers, we will use the standard string_of_int:let show = string_of_int;
sum takes a list of numbers and return the sum; think of this as a fold that apply the + operator starting with 0 as the initial value:let sum = List.fold_left((+), 0);
map and tail are the Haskell equivalents of respectively List.map and the List.tl functions:let map = List.map;
let tail = List.tl;
read is the reverse of show: it takes a string and converts it to a number, so what we need here is int_of_string:let read = int_of_string;
words split a string by white spaces, we can apply the Str.split and Str.regexp functions:let words
= Str.split
@@ Str.regexp("[ \t\n]+");
Notice how here I’ve used the
@@application operator:
a @@ bis equivalent toa(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
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.
here I use indentation for people reading on a smartphone. ↩︎