Functional FizzBuzz in Elixir

Inspired by last Erlang Thursday by Steven Proctor - Functional fizzbuzz I decided to give it a try and see how a FizzBuzz implementation without a modulus operator might look like in Elixir.

Here we go:

defmodule FizzBuzz do
  def print(n) do
    generate
    |> Stream.take(n)
    |> Stream.each(&IO.puts/1)
    |> Stream.run
  end

  def generate do
    fizzes = Stream.cycle(["Fizz", "", ""])
    buzzes = Stream.cycle(["Buzz", "", "", "", ""])

    Stream.zip(fizzes, buzzes)
    |> Stream.map(&concat/1)
    |> Stream.with_index
    |> Stream.map(&translate/1)
    |> Stream.drop(1)
  end

  defp concat({f, b}), do: f <> b

  defp translate({"",   n}), do: to_string(n)
  defp translate({str, _n}), do: str
end

It is generally straightforward, but there are some peculiarities, I’d like to explain. I know one of the problems in Erlang’s implementation was lack of lazy streams and functions such as cycle - in Elixir we don’t have this problem because Stream module exists. On the other hand using :lists.zipwith/3 in Erlang allowed to handle zipping and combining terms in one go. In Elixir we need two steps - first we use Stream.zip/2 (or Stream.with_index/1) and only later we can map the resulting tuple to the format we need.

The call to Stream.drop/2 is needed because in Elixir indexing starts with 0 and we want our first term to be a result of fizzbuzzing a 1. TO avoid this issue, we could generate a third stream to give us numbers starting with 1, and zip that:

def generate2 do
  fizzes  = Stream.cycle(["", "", "Fizz"])
  buzzes  = Stream.cycle(["", "", "", "", "Buzz"])
  indexes = Stream.iterate(1, &(&1 + 1))

  Stream.zip(fizzes, buzzes)
  |> Stream.map(&concat/1)
  |> Stream.zip(indexes)
  |> Stream.map(&translate/1)
end

I’m not entirely sure which one is clearer, and easier to grasp.

Where is my comment box!?

I don't do traditional comments, but you're welcome to send me an email to michal at muskala dot eu, and I'll publish it at the bottom of the article as a comment