53
votes

In Ruby, if one were defining constants in classes, they would define them using all caps. For example:

class MyClass
  MY_FAVORITE_NUMBER = 13
end

How do you do this in Elixir? And if no such equivalent exists, how do you get around the problem of magic numbers in Elixir?

6

6 Answers

56
votes

You can prepend your variable name with @:

defmodule MyModule do
  @my_favorite_number 13
end

Here are the docs

12
votes

Another approach to defining constants is one that I took with the wxErlang header files. That is, you can simply define a single line function that returns the constant value for you. Like so:

  def wxHORIZONTAL, do: 4
  def wxVERTICAL, do: 8
  def wxBOTH, do: (wxHORIZONTAL ||| wxVERTICAL)

and here's another example from the same source code:

 # From "defs.h": wxDirection
  def wxLEFT, do: 16
  def wxRIGHT, do: 32
  def wxUP, do: 64
  def wxDOWN, do: 128
  def wxTOP, do: wxUP
  def wxBOTTOM, do: wxDOWN
  def wxNORTH, do: wxUP
  def wxSOUTH, do: wxDOWN
  def wxWEST, do: wxLEFT
  def wxEAST, do: wxRIGHT
  def wxALL, do: (wxUP ||| wxDOWN ||| wxRIGHT ||| wxLEFT)

As you can see it makes it a little easier to define a constant in terms of another constant. And when I want those constants in a different module all I need to do is to require WxConstants at the top of the module. This makes it much easier to define a constant in one place and use it in several others--helps a lot with DRY.

By the way, you can see the repo here if you're curious.

As I say, I add this answer mostly for sake of completeness.

12
votes

Looking around github I see this being used which appeals to me. Allows the constant to be accessed from other modules.

ModuleA
  @my_constant 23

  defmacro my_constant, do: @my_constant


ModuleB
  Require ModuleA
  @my_constant ModuleA.my_constant

Yet again simple and facinating elixir solution

10
votes

Maybe you define a constants module file and in it you can define macros for this like so

defmodule MyApp.Constants do
  defmacro const_a do
    quote do: "A"
  end
end

You use it by importing it into any other module

defmodule MyApp.ModuleA do
  import MyApp.Constants

  def get_const_a do
    const_a()
  end 
end

The benefit is also that you don't incur any runtime cost as well as the advantage of using it in case matches

case something do
  const_a() -> do_something
  _ -> do_something_else
end
9
votes

Elixir modules can have associated metadata. Each item in the metadata is called an attribute and is accessed by its name. You define it inside a module using @name value. And is accessed as @name

defmodule Example
  @site 'StackOverflow' #defining attribute

  def get_site do
    @site #access attribute
  end 
end

Remeber this works only on top level of a module and you cannot set a module attribute inside a function definition.

2
votes

I wanted to add how I've started doing constants, which is similar to @Onorio Catenacci's answer, but uses quoting:

defmodule IbGib.Constants do

  @doc """
  Use this with `use IbGib.Constants, :ib_gib`
  """
  def ib_gib do
    quote do
      defp delim, do: "^"
      defp min_id_length, do: 1
      # etc...
    end
  end

  @doc """
  Use this with `use IbGib.Constants, :error_msgs`
  """
  def error_msgs do
    quote do
      defp emsg_invalid_relations do
        "Something about the rel8ns is invalid. :/"
      end
      # etc...
    end
  end

  @doc """
  When used, dispatch to the appropriate controller/view/etc.
  """
  defmacro __using__(which) when is_atom(which) do
    apply(__MODULE__, which, [])
  end
end

And then you use it like this at the top of the module where you want to consume them:

use IbGib.Constants, :ib_gib # < specifies only the ib_gib constants
use IbGib.Constants, :error_msgs

# ... then in some function
Logger.error emsg_invalid_relations

I got this with how Phoenix does import/use clauses with MyApp.Web. I'm nowhere near an Elixir expert, but with this method you can import just those constants that you want and you don't need to prefix them with any namespacing/scoping. This way, you can pick and choose the individual groups of constants easily.

With straight functions, (I think) you would have to break them up into multiple modules and then import the module.

I don't know the optimization ramifications of this vs direct module functions, but I thought it was pretty neat - especially for practice between the various ways to "import" things in Elixir (import, use, alias, require is very confusing as a beginner coming from other languages where this is a single using or import statement).

EDIT: I've changed the constant def declarations to defp. This is because when multiple modules import the constants file, there is an ambiguity conflict. Changing them to privately scoped functions avoids this conflict. So each module has its own "private copy" of the same constant.