Incerteza
Incerteza

Reputation: 34884

Enums in Elixir

I want to create a enum in Elixir. Is it possible? By enum I mean enums from C/C++ or Ruby or in many other languages. I'm aware of the Enum module but that's just a module -- a container for functions basically.

Upvotes: 16

Views: 11787

Answers (5)

Xawe
Xawe

Reputation: 21

I dont know if it is a good idea, but you can create a module (CustomEnum) where you define functions as Enums.

defmodule CustomEnum do
  def monday, do: 0
  def tuesday,do: 1
end

Then, you can call CustomEnum.monday and get value 0

Write all this functions could be boring, so you can create a list of tuples and dynamically create your function for each item. EX>

defmodule CustomWeekDaysEnum do
  weekdays = [ 
    monday: {0},
    tuesday: {1},
    wednesday: {2},
    thursday: {3}
    ....
  ]

  #creating functions dynamically
  for{function_name, {value} <- weekdays do
    def unquote(function_name)(), do: unquote(value)
  end
end

Now you call CustomWeekDaysEnum.wednesday and get 2, or CustomWeekDaysEnum.thursday and get 3.

And now, if you need a new Enum item, just put it in weekdays List and your function will be created at compile time.

weekdays is a list of tuples, so your enums can have multiple values inside. you just have to change unquoted def to return the tuple or just specific values

Just remember: Functions dynamically created are cool but your code may become harder to read.

You can find more about it in Saša Jurić post

The Erlangelist

Upvotes: 2

pi3
pi3

Reputation: 1245

You could use for example this library: jcomellas/ex_const. It allows you to define enums like this:

enum <name> do
  <key_1> <value_1>
  <key_2> <value_2>
  [...]
end

Sample code (taken from documentation on GitHub):

Definition:

enum country_code do
  argentina "AR"
  italy     "IT"
  usa       "US"
end

Usage:

require Settings
import Settings
value = "AR"
case value do
  country_code(:argentina) ->
    {:ok, "Argentina"}
  country_code(:italy) ->
    {:ok, "Italy"}
  code when code == country_code(:usa) ->
    {:ok, "United States"}
  _ ->
    {:error, {:must_be_one_of, country_codes()}}
end

Upvotes: 1

zzats
zzats

Reputation: 146

If I understand you correctly, you are looking for a way to use enumeration in Elixir. You can do this with an instance of a Map using :atoms as keys.

 weekdays = [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday]
    |> Enum.with_index 
    |> Map.new

Or a bit more manually

weekdays = %{
    monday: 0,
    tuesday: 1,
    wednesday: 2,
    thursday: 3,
    friday: 4,
    saturday: 5,
    sunday: 6}

If you're using :atoms as keys, the short-hand access syntax is:

iex> weekdays.monday
0
iex> weekdays.friday
4

But usually using atoms such as :error or :ok doesn't require you to do these declarations.

Upvotes: 13

I think the short answer is No. If by enum, you mean a static user defined type enforced at compile time, (i.e. c enums), then the answer is definitely no.

The long answer is that there are only a fixed number of basic data types in the underlying VM of Elixir/Erlang. There are the basic data types:

[Atom, Integer, Float, BitString, Regexp, PID, Function, Reference, Port] 

and the 3 Container types.

[Tuple, List, Map]

You can compose these types to create very complex data structures, but if you want to apply "rules" to the behaviour of those composed types, you need to create a library or module of functions for manipulating those types according to the rules.

Since Elixir is a dynamic language, it's is always possible to "reach" into any complex data type and completely transform it. There's no way to enforce rules on values unless you do that through a set of defined functions. My guess is that what you're looking for is a FixedSet type than can only take values from a pre-defined list of values. That would be relatively straightforward to implement as a module, but writing a module of functions( and likely some macros) is the only way to accomplish that kind of functionality in Elixir.

Upvotes: 3

Patrick Oscity
Patrick Oscity

Reputation: 54674

You could just use atoms. For static analysis, you can define a type that accepts only certain values and use that in your typespecs. You can then check your code with dialyxir for example.

@type state :: :a | :b | :c

@spec set_state(pid, state) :: :ok | {:error, term}
def set_state(pid, state)
  # ...
end

@spec get_state(pid) :: state
def get_state(pid)
  # ...
end

Upvotes: 15

Related Questions