Elixir:修改模块属性的值

Wan*_*ker 9 elixir

是否有可能实现以下行为,其中一个人试图改变模块属性的值来改变模块方法的行为?

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
Run Code Online (Sandbox Code Playgroud)

它抛出错误

** (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
Run Code Online (Sandbox Code Playgroud)

如果这是不可能的(因为Elixir中的所有内容都应该是不可变的),是否有任何Elixir方法可以实现类似的行为.

Paw*_*rok 17

这是不可能的,因为属性仅在编译该特定模块之前存在.编译模块时,所有属性都被内联并被遗忘,因此,当您能够从该模块调用函数时,就无法再修改属性.

这段代码应该更清楚地表明这一点:

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
Run Code Online (Sandbox Code Playgroud)

请注意,您可以在模块尚未编译时更改属性的值(例如在模块的主体中​​),只需再次设置即可,就像我在此处设置@attr时一样2.

顺便说一句,您似乎想要实现的目标可以通过以下方式轻松完成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
Run Code Online (Sandbox Code Playgroud)

Elixir的一个好的经验法则是,无论何时你需要跟踪一些可变状态,你都需要有一个包装该状态的进程.


Gja*_*don 5

在Elixir中,模块并不意味着在面向对象的编程语言中存储像对象/类那样的状态.模块属性更像是一个常量,并且一旦编译了模块就无法更改其值,除非通过函数公开,否则无法从外部访问它.

Pawel为如何实现类似行为提供了一些很好的选择.这是另一个更简单的,因为您不需要在另一个进程中存储状态.

defmodule Adder do
  @num_to_add 10

  def addTo(input, num_to_add \\ @num_to_add), 
    do: input + num_to_add
end
Run Code Online (Sandbox Code Playgroud)

在上面的方法中,我们只是将module属性设置为默认值.如果我们想要覆盖它,只需提供第二个参数.