After messing around with dependency injection frameworks in the first part, let’s see how we can take our LotR example and unclutter it by using data-oriented functional programming in Elixir!

Remember Frodo from last time? I already mentioned that Frodo’s data can be merged with Sam’s because they basically do almost everything together - which means that when we use their data for calculations, we will most likely need both of them for meaningful results.
But not only that, no, we also have duplicated data! It’s hidden (well, on purpose) in the AbstractHobbit superclass: The two of them have a certain value for hunger and for miles walked. Since they always march together, however, these two values will more or less stay the same - or at least we’d have to make sure to keep them in sync, so that Frodo for example doesn’t hunger more and walk less while Sam is already at the Mount Doom.

Let’s revisit the data structure from last time:

The Baggins/Gamgee Complex, Revisited
1defmodule Companions do
2  defstruct(
3    hunger_level: 0,         # they hunger together
4    miles_walked: 0,         # they march together
5    ring:         :the_ring, # ...hm... this should be stored somewhere else
6    provisions:   100)       # and they share their bread!
7end

Note that this small elixir snippet tells the compiler to structurally check maps annotated with Companions. It’s not very crucial for our logic later on, since we’ll treat input as any kind of map having defined certain keys. However, it illustrates nicely how to define named datastructures in Elixir. They are still a map - but define an additional __struct__ key that holds the atom of the module (Companions in this case).

Now, with object oriented thinking, while designing the methods for marching and eating etc., you would probably come up with some kind of functionality that is shared between the two Hobbit objects and put that in the AbstractHobbit class, and some kind of functionality that is very Frodo or Sam specific and pack that in their respective classes. You would also need to reason about the dependency between Frodo and Sam, and how provisions are distributed between them etc.
By reducing the data we work with to a minimum, however, and by arranging it in a very simple datastructure, we can implement this logic in a rather straightforward way without ever having to think of inheritance, encapsulation etc.

But before we do that, let’s make very sure - down to a specification of sorts - that we know exactly what kind of functionality we need. As opposed to thinking in terms of objects and taking their point of view (i.e. what they can do, whom they can interact with, what kind of state they have etc.), we want to think like a computer, that is, in terms of data transformations (what do I have as an input, what do I need to produce as an output).

  • we want the Hobbits to reach mount doom by marching (e.g. let’s define the goal to be at 100 miles)
    • marching a mile will increase the hunger level, let’s say by 2 units, but
    • marching is only possible if the hunger level does not exceed a certain value, let’s say 100
    • if the hobbits are too hungry (see above) they die and cannot complete their mission *BAM BAM BAM!*
  • the hunger level can be decreased by eating provisions, which should be measured in the same units as hunger
    • comsuming X provisions decreases the hunger by X
    • the hobbits cannot eat all of their provisions at once, let’s say the maximum is 2 units
  • in all the above points, common sense applies (i.e. hunger and provisions can not be negative etc.)

In Elixir, we can more or less take specifications like this and put it into code:

'Specification-Driven-Programming' ?
 1defmodule Walking do
 2  # the hunger level should not exceed 100, otherwise fail!
 3  def walk(%{hunger_level: hunger}) when hunger > 100,
 4  do: {:error, "Unable to walk! Dying of hunger - GAME OVER"}
 5
 6  # marching increases the hunger level
 7  def walk(%{hunger_level: hunger, miles_walked: miles} = creature) do
 8    {:ok, %{creature |
 9      hunger_level: hunger + 2,
10      miles_walked: miles + 1}}
11  end
12end
13
14defmodule Feeding do
15  # common sense and such
16  def feed(%{hunger_level: hunger, provisions: provisions} = creature)
17  when hunger <= 0 or provisions <= 0, do: creature
18
19  # consuming provisions decreases the hunger
20  def feed(%{hunger_level: hunger, provisions: provisions} = creature) do
21    # we cannot eat more than 2 provisions at a time (if available and needed)
22    eaten = 2 |> min(hunger) |> min(provisions)
23    %{creature |
24      hunger: hunger - eaten,
25      provisions: provisions - eaten}
26  end
27end

Okay, let’s take a step back. So where is Sam? And, more importantly, where are Frodo, Gandalf and the rest?

Who cares? We now only require datasets that can store hunger and miles values for walking around - it doesn’t really matter whether they are associated with a Hobbit-like entity or something like an Orc.
This means we just enabled Sauron’s army to march around, given enough provisions. We also decoupled walking from food consumption - if you’re modelling a magical being that feeds on the souls of its enemies, it can still use our walking logic, provided that it can actually hunger.

In a real application this can be used in many different ways. Consider a Hobbit situation:

The Journey
 1defmodule Journey do
 2  import Feeding
 3  import Walking
 4
 5  def time_passes(%Companions{} = companions), do: time_passes({:ok, companions})
 6
 7  def time_passes({:ok, %Companions{miles_walked: miles}})
 8  when miles >= 100, do: "Ring destroyed, all good! - GAME OVER"
 9
10  def time_passes({:ok, %Companions{} = companions) do
11    companions
12    |> consider_eating
13    |> walk
14    |> time_passes
15  end
16
17  def time_passes({:error, reason}), do: reason
18
19  def consider_eating(%Companions{
20      hunger_level: hunger,
21      provisions: provisions} = companions)
22  when (hunger > 10) and provisions > 0, do: feed(companions)
23
24  def consider_eating(companions), do: companions
25end
26
27# start the journey and wait for what happens...
28Journey.time_passes(
29  %Companions{
30    hunger_level: 0,
31    miles_walked: 0,
32    provisions: 100})
33# "Unable to walk! Dying of hunger - GAME OVER"
34
35# ...guess how much provisions we need to provide
36# the Hobbits with to survive the journey...

Alright, this concludes my little rant about dependency injection and object orientation. If you’d like to read more about the design approach used in this article, I’d recommend looking into entity-component-systems, which is covered in great detail and with loads of examples by Adam on his blog.

And in case you are wondering what the entity-systems topic has to do with this post: What we created here is the beginning of a component (the datastructure) and two systems operating on that component (the modules for walking and feeding) - a Hobbit entity for example might consist of several such components, usually tied together only by the same entity ID.