/ elixir

fizzbuzz in elixir using OTP and GenServer

Speaking of Erlang/Elixir the first thing that comes to mind is OTP. The erlang veterans would definitely playing around with it on a daily basis, but to the novice, it would sound a relatively strange term, and would be wondering what this is. So I thought of scribbling down something around this topic, to give a general idea on OTP and GenServer.

What is OTP?

So if we go by the official words, OTP — the Open Telecom Platform can be defined as:

“a complete development environment for concurrent programming”,

usually consisting of an Erlang compiler and interpreter, a database server (Mnesia), an analysis tool (Dyalizer).

Behaviours

One of the central design principles of Erlang/OTP are application patterns, or behaviours as the grey-beards calls them. A set of common tasks would be defined as a generic implementation, and we just need to add implementation specific code into a callback module which exports a set of specific functions. With Elixir most of these implementations comes in built in, which makes the developers life easy. so we just need to override only those which matters for us.

A FizzBuzz server using GenServer

FizzBuzz is an easy piece of code to get started with something, and I am gonna use the same here.

Elixir comes with a really cool build tool named mix that provides tasks for creating, compiling, testing your application, managing its dependencies.

Lets use mix to create a new project.

$ mix new fizzbuzz --module FizzBuzz

Running mix new creates a whole application using the default template for us.

So the next thing to do is, use GenServer in our application. So add the following to your
lib/fizzbuzz.ex file. I am also gonna use the Logger in later parts for debugging the code, so lets just add that too.

use GenServer
require Logger

So once we have everything in place, the next thing we would want is to have an API for our clients via which they can communicate to the server. So let's go ahead and write our APIs

Next we define the API for our clients.

def start_link do
   GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end

def get(n) do
  GenServer.call(__MODULE__, {:print, n})
end

def print(n) do
  GenServer.cast(__MODULE__, {:print, n})
end

Explanation:

FizzBuzz.start_link/0 is just a wrapper around GenServer.start_link/3 which starts the server as a linked process. Usually, the other functions refer to our server using the PID, but it's a bit odd to use the PID in every case. Alternatively we can start our server by passing the name option. In our case we will pass the name of the module (__MODULE__)

Our server mainly has 2 interfaces, namely, FizzBuzz.get/1 and FizzBuzz.print/1. The input for these is a number and would return the value of the FizzBuzz as the output.

A GenServer mainly supports 2 request types: calls and casts.
calls is synchronous, which means it is supposed to send something back to the client (our get function), on the other hand casts is asynchronous and doesn’t necessarily return anything to the client (our print function).

It's usually considered as a best practice to wrap the GenServer interface in our own client APIs.

So moving on, we now need callbacks for the GenServer.

def init(:ok) do
  Logger.debug "FizzBuzz server started"
  {:ok, %{}}
end

def handle_call({:print, n}, _from, state) do
  {:ok, fb, state} = fetch_or_calculate(n, state)
  {:reply, fb, state}
end

def handle_cast({:print, n}, state) do
  {:ok, fb, state} = fetch_or_calculate(n, state)
  IO.puts fb
  {:noreply, state}
end

init/1 gets called by GenSever.start_link/3 and returns a tuple of the form {:ok, state}. In our specific case the state is a simple Elixir map (%{}).

handle_call/2 and handle_cast/2 are the core of our application. We use pattern matching on the first argument to specify that we handle messages of the form {:print, n}. The private function fetch_or_calculate/2 retrieves or calculates the value, and we then return the appropriate response. In the case of a call it will have the form {:reply, response, state}, whereas for a cast it will be {:noreply, state}. These are GenServer conventions, the documentation lists all possible answer types for every request type.

This is what the private helper looks like:

defp fetch_or_calculate(n, state) do
  if Dict.has_key?(state, n) do
    Logger.debug "Fetching #{n}"
    {:ok, fb} = Dict.fetch(state, n)
  else
    Logger.debug "Calculating #{n}"
    fb = fizzbuzz(n)
    state = Dict.put(state, n, fb)
  end
  {:ok, fb , state}
end

fetch_or_calculate/2 checks if the FizzBuzz value for n has been calculated before. If it has, we fetch it from the dictionary. If the value hasn’t been computed before, we do so and update the state by adding the newly computed value to it (Dict.put(state, n, fb)). Finally, we return a tuple of the form {:ok, value, state} which we pattern match against in the handler functions.

That’s it, our FizzBuzz server is now ready for action! Here’s the complete code:

defmodule FizzBuzz do
  use GenServer
  require Logger

  ## Client API

  def start_link do
     GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  def get(n) do
    GenServer.call(__MODULE__, {:print, n})
  end

  def print(n) do
    GenServer.cast(__MODULE__, {:print, n})
  end

  ## Server Callbacks

  def init(:ok) do
    Logger.debug "FizzBuzz server started"
    {:ok, %{}}
  end

  def handle_call({:print, n}, _from, state) do
    {:ok, fb, state} = fetch_or_calculate(n, state)
    {:reply, fb, state}
  end

  def handle_cast({:print, n}, state) do
    {:ok, fb, state} = fetch_or_calculate(n, state)
    IO.puts fb
    {:noreply, state}
  end

  defp fetch_or_calculate(n, state) do
    if Dict.has_key?(state, n) do
      Logger.debug "Fetching #{n}"
      {:ok, fb} = Dict.fetch(state, n)
    else
      Logger.debug "Calculating #{n}"
      fb = fizzbuzz(n)
      state = Dict.put(state, n, fb)
    end
    {:ok, fb , state}
  end

  defp fizzbuzz(n) do
    case {rem(n, 3), rem(n, 5)} do
      {0, 0} -> :FizzBuzz
      {0, _} -> :Fizz
      {_, 0} -> :Buzz
      _      -> n
    end
  end
end

Time to use our code. In the project directory, fire up an iex session in the context of our application with the following command:

iex -S mix

Now we can start a server and compute some values:

FizzBuzz.start_link
1..100 |> Enum.map(&FizzBuzz.print/1)

Here’s the output generated by that command. Due to the asynchronous nature of the requests the outout, log messages and the function’s return value are interleaved:

4

10:54:58.026 [debug] Calculating 4
Buzz
[:ok, :ok, :ok]

10:54:58.028 [debug] Calculating 5
Fizz

Note how despite running in a separate process, the output of IO.puts happened in our iex session, since that’s the current group leader.

If we now try to fetch an already computed value (FizzBuzz.get(5)), we can see that it’s actually retrieved from the cache:

10:58:56.774 [debug] Fetching 5
:Buzz

Summary

This blog post explores the concepts of OTP, and how to leverage those concepts and build server applications.

Now our FizzBuzz application can be extended offering features like supervision and hot code swapping or can be deployed as part of a bigger Elixir application

Manu S Ajith

Manu S Ajith

Tech Entrepreneur, dating Elixir, in long-term ❤️ w/ Ruby, had multiple one night stands w/ Go. Into functional paradigms DDD/CQRS/EventSourcing architecture these days. @manusajith on the interwebs

Read More