Over a million developers have joined DZone.

''Tell, Don't Ask'' in Elixir: A Story of Pattern Matching

This look at Elixir uses a pattern matching example to highlight the desire for more declarative code over imperative code, especially when transforming data.

· Java Zone

Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code! Brought to you in partnership with ZeroTurnaround.

“Tell, Don’t Ask” is a well-covered topic within object-oriented programming communities. Its goal? Encourage encapsulation by having the caller tell an object to do something instead of checking on state and acting upon it. Almost at odds with the control couple code smell, our goal is to have the caller issue explicit commands without concerning itself with object state.

“Tell, Don’t Ask” in Elixir

Is Elixir object-oriented? From a paradigm perspective, Elixir is a functional language when looking at aspects like immutability, pattern-matching, and functions with inputs and outputs, focused on the sending of messages to “objects” directly. How does “Tell, Don’t Ask” translate?

Thinking about the goal, let’s do some mental mapping. In OOP, objects are a blueprint with information containing behavior (methods) and data (state). In FP, we have functions organized within modules, with state being captured in various values (e.g. Elixir’s Maps or Structs). We want to avoid having the caller (a function) dictate paths based on information present in our data.

Let’s write out some non-idiomatic Elixir and see what we can improve.

defmodule Game.Lobby do
  def add_player(%{game: game} = lobby, player) do
    new_player = cond do
      is_binary(player) ->
        %Game.Player{name: player, id: Game.Player.generate_id}
      is_map(player) ->
        %Game.Player{} |> Map.merge(player)
      true ->
        %Game.Player{}
    end

    %{lobby |
      game: %{game | players: game.players ++ [new_player]}}
  end
end

defmodule Game.Player do
  defstruct id: 0, name: "New player", active: true

  def generate_id do
    UUID.uuid4()
  end
end


Game.Lobby.add_player/2 doesn’t feel right. There’s a significant amount of feature envy as it cares about the various shapes of player and how to construct a %Game.Player{}. Also, why is Game.Player.generate_id/0 public? It seems all Game.Lobby.add_player/2 should care about is managing its own structure (the final two lines of the function).

Instead of having Game.Lobby.add_player/2 care about constructing players, generating IDs, and so on, let’s tell Game.Player to handle that instead:

defmodule Game.Lobby do
  def add_player(%{game: game} = lobby, player) do
    %{lobby |
      game: %{game | players: game.players ++ [Game.Player.new(player)]}}
  end
end

defmodule Game.Player do
  defstruct id: 0, name: "New player", active: true

  def new(name) when is_binary(name), do: new(%{name: name, id: generate_id})
  def new(a)    when is_map(a),       do: %__MODULE__{} |> Map.merge(a)
  def new(_),                         do: %__MODULE__{}

  defp generate_id, do: UUID.uuid4()
end


Here, we move player generation to the Game.Player module, where it can determine how best to generate the struct instead of Game.Lobby.add_player/2.

Write Declarative (Not Imperative) Code

By moving player creation logic from Game.Lobby.add_player/2 to Game.Player.new/1, we were able to call a single function to take the appropriate action based on data. It is important to note that the data it’s acting upon specifically is the behavior to construct a %Game.Player{}.

This becomes more important when using the pipe operator, which shines as a way to transform data.

“Tell, don’t ask” is a way to encourage developers to write declarative code instead of imperative code. Imperative code asks questions before making decisions; declarative code issues a command and expects it to be done correctly.

The Java Zone is brought to you in partnership with ZeroTurnaround. Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code!

Topics:
elixir ,java ,pattern matching ,tutorial ,declarative programming ,imperative programming

Published at DZone with permission of Josh Clayton, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}