While I was doing some Elixir lately, I came across a coding problem that requires an enum (enum as Enumerated data type and not as Enumerable)

In my code I have a TextEditor structure that has a name, an is cross_platfrom boolean and an is native boolean.

defmodule TextEditor do  
  defstruct [:name, :cross_platfrom, :native]
end  

I then need to have four different types of TextEditors; SublimeText, TextMate, Vim, and Atom.

After creating one of these editors, I pass it to the print method, which will just print the contents of that editor.

def print(editor) do  
  ...
end  

We can model the enumerated type in three ways, lets see them bellow.

Using functions

In this first method, we can model each enumerated type as a function in a TextEditors module. Each of our four types will have a function with its name that returns a structure containing that editor info.

defmodule TextEditors do  
  def vim, do: %TextEditor{name: "Vim", cross_platfrom: True, native: True}

  def text_mate, do: %TextEditor{name: "TextMate", cross_platfrom: False, native: True}

  def sublime_text, do: %TextEditor{name: "Sublime Text", cross_platfrom: True, native: True}

  def atom, do: %TextEditor{name: "Atom", cross_platfrom: True, native: False}
end  

If we want to get the Vim editor, we call TextEditors.vim. Our print method now looks like this:

def print(editor) do  
  IO.puts "#{editor.name} is cool"
end  

Using module attributes

One of the usages of module attributes is to act as constants. Here we are using an attribute as a storage to a map containing the different text editors and a get method that given a string, will get the text editor with that value from the map.

defmodule TextEditors do  
  @editors %{
    "vim" => 
      %TextEditor{name: "Vim", cross_platfrom: True, native: True},
    "text_mate" => 
      %TextEditor{name: "TextMate", cross_platfrom: False, native: True},
    "sublime_text" => 
      %TextEditor{name: "Sublime Text", cross_platfrom: True, native: True},
    "atom" => 
      %TextEditor{name: "Atom", cross_platfrom: True, native: False}
  }

  def get(name), do: @editors[name]
end  

This new approach needs a change to how we call print:

print(TextEditors.get("vim"))  

Using macros

This approach here is an extension on the previous one. We start by creating an EnumeratedType module that only defines a __using__ macro.

Using a macro

defmodule EnumeratedType do  
  defmacro __using__(x) do
    quote do
      def get(key) do
        unquote(x)[key]
      end
    end
  end
end  

The macro adds a get method to the module that uses it.

We can refactor TextEditors module from the previous example to use EnumeratedType.

defmodule TextEditors do  
  use EnumeratedType, %{
    "vim" => 
      %TextEditor{name: "Vim", cross_platfrom: True, native: True},
    "text_mate" => 
      %TextEditor{name: "TextMate", cross_platfrom: False, native: True},
    "sublime_text" => 
      %TextEditor{name: "Sublime Text", cross_platfrom: True, native: True},
    "atom" => 
      %TextEditor{name: "Atom", cross_platfrom: True, native: False}
  }
end  

To get an editor now, we do the following:

TextEditors.get("vim")  
TextEditors.get("sublime_text")  

Using Modules and Behaviours

In the previous approaches, the enum value was passed around as function calls; if you wanted vim you call TextEditor.vim and atom TextEditor.atom.

In this last approach, we are going to implement enum types as module instead. We will do that using behaviours.

First, we extend TextEditor to expose a behaviour.

defmodule TextEditor do  
  defstruct [:name, :cross_platfrom, :native]

  @type t :: %TextEditor{}
  @callback value :: TextEditor.t
end  

We then create a module for each of our text editor, each of these modules adhere to the TextEditor behaviour.

defmodule TextEditor.Vim do  
  @behaviour TextEditor
  def value, do: %TextEditor{name: "Vim", cross_platfrom: True, native: True}
end

defmodule TextEditor.SublimeText do  
  @behaviour TextEditor
  def value, do: %TextEditor{name: "Sublime Text", cross_platfrom: True, native: True}
end

defmodule TextEditor.TextMate do  
  @behaviour TextEditor
  def value, do: %TextEditor{name: "TextMate", cross_platfrom: False, native: True}
end

defmodule TextEditor.Atom do  
  @behaviour TextEditor
  def value, do: %TextEditor{name: "Atom", cross_platfrom: True, native: False}
end  

Lets update our print method to accept a module instead.

def print(editor) do  
  IO.puts "#{editor.value.name} is cool"
end  

Now print is callable with a module instead of a mothod.

print(TextEditors.Vim)  
print(TextEditors.Sublime)  

I personally prefer the macro approach since it's nice and concise, and you can extend the get method in the macro by adding compile-time and run-time sanity checks.