What does this F# code look like in Erlang – Part 2 of N

Relat­ed Posts

 

if-else

In F#, if-else if-else con­trol flow is expressed in the form if Exp then Exp elif Exp then Exp else Exp:

let f n =

    if n = 42 then

        printfn “%d is the answer to the ulti­mate ques­tion of life, the uni­verse and every­thing” n

    elif n % 2 = 0 then

        printfn “%d is even” n

    else

        printfn “%d is odd” n

In Erlang, there’s not explic­it else if or else claus­es, instead you just spec­i­fy mul­ti­ple claus­es and the last catch-all (true –> …) is Erlang’s equiv­a­lent of the ‘else’ clause.

f (N) –>

    if N =:= 42 -> io:format(“~p is the answer to the ulti­mate ques­tion of life, the uni­verse and every­thing”, [N]);

       N rem 2 =:= 0 -> io:format(“~p is even”, [N]);

       true -> io:format(“~p is odd”, [N])  % this is Erlang’s equiv­a­lent to an ‘else’

    end.

When you’re writ­ing Erlang you’re usu­al­ly dis­cour­aged from using ‘else’ or ‘true’ branch­es alto­geth­er and should instead replace them with if claus­es that cov­er all log­i­cal branch­es to help avoid mask­ing oth­er­wise hard-to-detect bugs.

 

match-with

The pre­ferred way (for me at least) to do con­di­tion­al branch­ing in F# is to use the match-with expres­sion rather than using ifs. The log­ic expressed in the func­tion f from the above exam­ple can eas­i­ly be expressed in a match-with instead:

let g n =

    match n with

    | 42 -> printfn “%d is the answer to the ulti­mate ques­tion of life, the uni­verse and every­thing” n

    | _ when n % 2 = 0 -> printfn “%d is even” n

    | _ -> printfn “%d is odd” n

Erlang’s coun­ter­part to match-with is the case-of expres­sion, which looks a lot like match-with:

g (N) –>

    case N of

        42 -> io:format(“~p is the answer to the ulti­mate ques­tion of life, the uni­verse and every­thing”, [N]);

        _ when N rem 2 =:= 0 -> io:format(“~p is even”, [N]);

        _ -> io:format(“~p is odd”, [N])

    end.

 

Type Con­ver­sions

In F#, you can cast a val­ue of type T into a val­ue of type V like this:

let n = int “42”

pro­vid­ed that there exists an explic­it cast oper­a­tor that lets you con­vert a val­ue of type T to type V.

In Erlang, type con­ver­sion is achieved with the help of built-in func­tions that are gen­er­al­ly named T_to_V where T and V are the names of the source and des­ti­na­tion types:

N = list_to_integer(“42”).

There are many oth­er such built-in func­tions, includ­ing: atom_to_binary, atom_to_list, binary_to_atom, list_to_atom, integer_to_list, …

From the above exam­ple, you might be won­der­ing why the built-in func­tion that con­verts a string to an inte­ger is called list_to_integer as opposed to string_to_integer. This is because there is no real string type in Erlang!

In Erlang, strings are lists of inte­gers whose ele­ments are all inte­gers that rep­re­sent print­able char­ac­ters:

1> [52, 50].

42”

2> % 2 is not a print­able char­ac­ter, so this is treat­ed as an inte­ger list

2> [52, 50, 2].

[52, 50, 2]

3> [ 104, 101, 108, 108, 111 ].

hel­lo”

4> % you can use the $ oper­a­tor to find out the inte­ger val­ue of a char­ac­ter

4> $/.

47

5> $n.

110

I’m sure many a devel­op­er has enjoyed a won­der­ful WTF moment when they realised the lack of a native string type in Erlang, but giv­en its ori­gin in the tele­com world where string manip­u­la­tion is sel­dom required it’s per­haps not sur­pris­ing that the lan­guage design­ers nev­er saw fit to make string manip­u­la­tion more nat­ur­al.

But seri­ous­ly, WTF!

 

Type Tests

Type test­ing is per­formed with the : ? oper­a­tor in F#:

let f (n : obj) =

    match n with

    | : ? int as nInt -> printfn “%d is an inte­ger” nInt

    | : ? float as nFloat -> printfn “%f is a float” nFloat

    | : ? string as nStr -> printfn “%s is a string” nStr

In Erlang, you can use a num­ber of built-in func­tions named is_T where T is the name of the type:

f (N) –>

    case N of

        _ when is_integer(N) -> io:format(“~p is an inte­ger”, [N]);

        _ when is_float(N) -> io:format(“~p is a float”, [N]);

        _ when is_list(N) -> io:format(“~p is a list”, [N])

    end.

 

Recur­sive Func­tions

A recur­sive func­tion in F# needs to be dec­o­rat­ed with the rec key­word, a fac­to­r­i­al func­tion in F# would look like this:

let rec fac n =

    match n with

    | 0 –> 1

    | _ -> n * fac(n — 1)

In Erlang, you can spec­i­fy mul­ti­ple over­loads (called func­tion claus­es) for the same func­tion in a style sim­i­lar to facts in Pro­log, recur­sion is a sim­ple mat­ter of hav­ing one or more of these claus­es call itself:

fac (N) when N =:= 0 -> 1;

fac (N) when N > 0 -> N * fac(N — 1).

You might notice that nei­ther imple­men­ta­tions above are tail recur­sive. To make tail recur­sive ver­sions of the above exam­ples, sim­ply add an accu­mu­la­tor to the func­tion, the F# ver­sion will prob­a­bly look some­thing along the lines of:

let fac n =

    let rec tail­Fac n acc =

        match n with

        | 0 –> acc

        | _ -> tail­Fac (n — 1) (n * acc)

       

    tail­Fac n 1

Notice how I imple­ment­ed the tail recur­sive func­tion as an inner func­tion to fac so that the con­sumers of fac have a nat­ur­al and easy to use API and don’t have to be aware that the accu­mu­la­tor needs to be ini­tial­ized with 1 for it to func­tion cor­rect­ly (which is an imple­men­ta­tion detail that one should not depend upon).

This type of encap­su­la­tion is easy to achieve in Erlang and the above will trans­late nice­ly into:

-module(recursion).

-export([fac/1]). % only expose the fac (N) func­tion to con­sumers of this mod­ule

fac (N) -> tail_fac(N, 1).

%% these func­tions are pri­vate to the mod­ule

tail_fac (0, Acc) -> Acc;

tail_fac (N, Acc) when N > 0 -> tail_fac(N — 1, N * Acc).

 

High­er Order Func­tions

In F#, the List mod­ule defines a num­ber of high-order func­tions that lets you per­form map, fil­ter, zip, etc. oper­a­tions against a list, e.g. to dou­ble every ele­ment in a list you can write:

let dou­bles = List.map (fun x -> x * 2) [1..10]

// you can also write the above as the fol­low­ing

let doubles2 = List.map ((*) 2) [1..10]

This code will look some­thing along the line of:

Dou­bles = lists:map(fun (X) -> X * 2 end, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).

With Erlang’s anony­mous func­tions you can also spec­i­fy mul­ti­ple func­tion claus­es like you can with a nor­mal func­tion and make use of when guards:

NewList = lists:map(fun (1) –> 10;

                                     (X) when X rem 2 =:= 0 -> X * 2;

                                     (X) -> X * 3 end,

                               [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).