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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With