25
votes

I have this module in elixir with an attribute:

defmodule MyAwesomeModule do
  @awesome_number 7

  # other stuff...
end

I'm unable to access @awesome_number outside the module. I've tried using the Module.get_attribute/2 method, but it throws this error:

iex(79)> Module.get_attribute(MyAwesomeModule, :awesome_number)
** (ArgumentError) could not call get_attribute on module MyAwesomeModule because it was already compiled
    (elixir) lib/module.ex:1101: Module.assert_not_compiled!/2
    (elixir) lib/module.ex:1016: Module.get_attribute/3

So right now, I'm wrapping the module attribute in a method to access it, but it doesn't really make sense to me. I could simply use the method and stop using the attribute all together:

defmodule MyAwesomeModule do
  @awesome_number 7

  def awesome_number, do: @awesome_number

  # other stuff...
end

So my question is, is there a better / proper way of doing this?

2

2 Answers

27
votes

AFAIK there is no way to access the module attributes outside of the given module. Defining a function to expose the module attribute is the way to go, just what you are already doing.

There still could be a good reason to keep the module attribute, instead of just using the function without the module attribute. It depends on the context. Keep in mind that the value stored in the module attributes is calculated at compilation time. That being said you could have different reasons to use or not to use the module attribute. Let's look at the following examples:

If the awesome_number have to be random generated every time when it's accessed, you have to go just with a function.

If the awesome_number needs to be computed (long time) and it doesn't have to change its value, then going with the module attribute + function to expose it, is the way to go.

Edit:

There is more to module attributes from what I've said earlier. They perform better than just functions. Here is an example and a quote from the elixir docs:

defmodule MyServer do
  @my_data 14
  def first_data, do: @my_data
  @my_data 13
  def second_data, do: @my_data
end

MyServer.first_data #=> 14
MyServer.second_data #=> 13

Notice that reading an attribute inside a function takes a snapshot of its current value. In other words, the value is read at compilation time and not at runtime. As we are going to see, this makes attributes useful to be used as storage during module compilation.

Using them with Module.register_attribute/3 (https://hexdocs.pm/elixir/Module.html#register_attribute/3) and especially with the accumulate: true option, makes them useful in more ways.

What I want to say is that they can be more useful than just being used as a constant.

17
votes

There is a way of "cheating" by using use and macros. Look at this example.

For instance suppose you define a module as:

defmodule AwesomeLibrary do  
  defmacro __using__(_) do
    quote do
      def print(s), do: IO.puts(s)
    end
  end
end 

Then, in some module you can use the use keyword in this way.

defmodule TestLibrary do  
  use AwesomeLibrary
end

The effect is that everything defined in the __using__ block is copied in the new module at compile time. So, in that case, you can use TestLibrary.print even if print is defined in another module.

This can be used to 'copy constants' too:

defmodule AwesomeLibrary do  
  defmacro __using__(_) do
    quote do
      @awesome_number 7

      def print(s), do: IO.puts(s)
    end
  end
end

An example of this that you can look at is the Timex library. It uses a dedicated module for constants that is imported whenever it is needed.

This seems to me the nicer way to share constants definitions around a big codebase.