Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elixir: Modifying value of module attribute

Tags:

elixir

Is it possible to achieve below behavior wherein one tries to change the value of a module attribute to alter the behavior of the module methods?

defmodule Adder do
  @num_to_add 10
  def addTo(input), do: input + @num_to_add
end

IO.inspect Adder.addTo(5)  # Prints 15

Adder.num_to_add = 20

IO.inspect Adder.addTo(5)  # Expect it to print 25

It throws below error

** (CompileError) hello.exs:8: cannot invoke remote function Adder.num_to_add/0 inside match
    (elixir) src/elixir_clauses.erl:26: :elixir_clauses.match/3

If this is not possible (as everything in Elixir is supposed to immutable), is there any Elixir-way of achieving similar behavior.

like image 670
Wand Maker Avatar asked Aug 06 '15 18:08

Wand Maker


2 Answers

This is not possible since attributes only exist up until compilation of that specific module. When the module is compiled all the attributes are inlined and forgotten about, so at the point you are able to call functions from that module it is no longer possible to modify the attributes.

This code should show this a bit more clearly:

defmodule Test do
  @attr 1
  @attr 2

  def attr do
    @attr
  end
end

IO.inspect Test.attr # => 2
Module.put_attribute(Test, :attr, 3)
IO.inspect Test.attr # => ** (ArgumentError) could not call put_attribute on module Test because it was already compiled

Note that you can change the value of the attribute while the module hasn't been compiled (for example in the module's body) simply by setting it again, like I do here when setting @attr to 2.

Incidentally what you seem to be trying to achieve can be done easily with an Agent:

defmodule Storage do
  def start_link do
    Agent.start_link(fn -> 10 end, name: __MODULE__)
  end

  def add_to(input) do
    Agent.get_and_update(__MODULE__, fn (x) -> {x + input, x + input} end)
  end
end

Storage.start_link
IO.inspect Storage.add_to(5) # => 15
IO.inspect Storage.add_to(5) # => 20

A good rule of thumb in Elixir is that whenever you need to keep track of some mutable state you will need to have a process wrapping that state.

like image 101
Paweł Obrok Avatar answered Sep 27 '22 16:09

Paweł Obrok


In Elixir, modules are not meant to store state like objects/classes do in object-oriented programming languages. The module attribute is more like a constant and its value can not be changed once the module has been compiled and is not accessible externally unless exposed through a function.

Pawel provides some good alternatives on how to achieve similar behavior. Here's another which is simpler since you will not need to store state in another process.

defmodule Adder do
  @num_to_add 10

  def addTo(input, num_to_add \\ @num_to_add), 
    do: input + num_to_add
end

In the above approach, we just set the module attribute as the default value. If we want to override that, just provide the second argument.

like image 24
Gjaldon Avatar answered Sep 27 '22 17:09

Gjaldon