Erlang FizzBuzz Showdown (pt2)

In my last post I documented some of the steps involved in building a simple FizzBuzz script in Erlang and compared the code to its equivalents in Python, Ruby and PHP. So far we've simply covered using a tail-recursive function to creat a list of numbers 1-100.

The next problem to tackle is checking whether a number is: just a number, a Fizz (divisible by 3), a Buzz (divisible by 5) or a FizzBuzz (divisible by 3 and 5). As I'm in the habit of using if statements to perform checks (I'm primarily a PHP developer) my first attempt looked like this:

fizzbuzz(I) ->
    if
        ((I rem 3 =:= 0 ) and (I rem 5 =:= 0 )) ->
            fizzbuzz;
        I rem 3 =:= 0 ->
            fizz;
        I rem 5 =:= 0 ->
            buzz;
        true ->
            I
    end.

This works just fine, but isn't a good example of writing Erlang in a functional manner, this is more like imperative programming in style. For example, it is not dissimilar to this python function:

# python

def fizzbuzz(i):
  if i % 3 == 0 and i % 5 == 0:
    return 'Fizzbuzz'
        
  elif i % 3 == 0:
    return 'Fizz'
        
  elif i % 5 == 0:
    return 'Buzz'
        
  else:
    return i

In Erlang (and I assume functional programming in general) Guards rather than if statements are far more appropriate. So, using the multiple entry points for a function and combining them with a guard I ended up with this:

fizzbuzz(N) when N rem 3 == 0, N rem 5 == 0 -> "FizzBuzz";
fizzbuzz(N) when N rem 3 == 0 -> "Fizz";
fizzbuzz(N) when N rem 5 == 0 -> "Buzz";
fizzbuzz(N) -> N.

Once I'd taken on board the functional approach the FizzBuzz function has not lost any readability (if anything this is easier to follow) and has become more compact and efficient. From what I've seen so far: Erlang programs rarely need if statements.

The next stage in the FizzBuzz program is to combine this with my earlier range function and loop through the numbers 1-100. Attempt 1 worked like so:

combine(To) ->
  F = (fun(I) ->  fizzbuzz(I) end),
  lists:map(F,range(1,To,[])).

Again, this works: the function combine takes in the number to be counted up to (e.g. 100), then a Fun is created (basically a mini function that can be passed into other functions as an argument) that is a copy of fizzbuzz, and then uses the inbuilt module/function lists/map to apply it to each number in the list returning a new list with the appropriate changes. But again this can be condensed to make better use of Elang's functional syntax.

combine(To) ->
  [fizzbuzz(X) || X 

The end result is the same but the function now uses a pattern matching/balancing style syntax to produce the new list. The process breaks down like this:
  • [] is the list notation, and using the pipes to separate the two haves of the process ([X || X ) means that the result of the right hand site is passed into the left (as X, formed by breaking the list down X ), forming a new list one element at a time.
  • The input list is formed using the range function on the right.
  • Then the fizzbuzz function processes the incoming elements of the new list on the left.

And that almost concludes my Erlang FizzBuzz program. However, it is returning a list rather than printing one line at a time. This is the module so far:

-module(fizzbuzz).
-export([fizzbuzz/1,range/3,combine/1]).

fizzbuzz(N) when N rem 3 == 0, N rem 5 == 0 -> "FizzBuzz";
fizzbuzz(N) when N rem 3 == 0 -> "Fizz";
fizzbuzz(N) when N rem 5 == 0 -> "Buzz";
fizzbuzz(N) -> N.

range(To,To,List) -> [To|List];
range(From,To,List) when From  range(From, To -1, [To|List]);
range(From,To,List) when From > To -> List.

combine(To) ->
  [fizzbuzz(X) || X 

And being used in the console:


Eshell V5.6.5  (abort with ^G)
1> c(fizzbuzz).
{ok,fizzbuzz}
2> fizzbuzz:combine(100).
[1,2,"Fizz",4,"Buzz","Fizz",7,8,"Fizz","Buzz",11,"Fizz",13,
 14,"FizzBuzz",16,17,"Fizz",19,"Buzz","Fizz",22,23,"Fizz",
 "Buzz",26,"Fizz",28,29|...]


Finally, this can be condensed one last time by turning the range function into a tool for applying the fizzbuzz function to each number recursively, printing the results to the console as it goes:


-module(fb_final).
-export([fizzbuzz/1,fbr/2]).

fizzbuzz(N) when N rem 3 == 0, N rem 5 == 0 ->
  io:format("FizzBuzz\n");

fizzbuzz(N) when N rem 3 == 0 ->
  io:format("Fizz\n");

fizzbuzz(N) when N rem 5 == 0 ->
  io:format("Buzz\n");

fizzbuzz(N) ->
  io:format([integer_to_list(N) | "\n"]).

fbr(To,To) -> fizzbuzz(To);
fbr(From,To) when From  fizzbuzz(From), fbr(From + 1, To).



And with that I'm pretty pleased. The fb_final module makes good use of the functional approach, is concise but still fairly easy to read. In conclusion I'm enjoying Erlang a great deal.

P.S:
There is one last small modification that makes fbr/range reusable allowing higher order functions to be passed into it:


range(To,To,F) -> F(To);
range(From,To,F) when From  F(From), range(From + 1, To,F).


This executes using the following console script (inserting fizzbuzz into range as a higher order function):


Eshell V5.6.5  (abort with ^G)
1> c(fb_final).
{ok,fb_final}
2> fb_final:range(1,15,fun(N) -> fb_final:fizzbuzz(N) end).
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
ok



Done and dusted! And here are some other ways of getting FizzBuzz done in a functional style.
Meta