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