Elixir:修改模块属性的值
Elixir: Modifying value of module attribute
是否可以实现以下行为,其中一个人试图更改模块属性的值以改变模块方法的行为?
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
抛出以下错误
** (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
如果这不可能(因为 Elixir 中的所有内容都应该是不可变的),是否有任何 Elixir 方法可以实现类似的行为。
这是不可能的,因为属性只存在于该特定模块的编译之前。当模块被编译时,所有属性都被内联并被遗忘,所以当你能够从该模块调用函数时,不再可能修改属性。
此代码应该更清楚地显示这一点:
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
请注意,您可以在模块尚未编译时(例如在模块的主体中)更改属性的值,只需再次设置它即可,就像我在此处将 @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
Elixir 中的一个好的经验法则是,无论何时您需要跟踪某些可变状态,您都需要有一个包装该状态的进程。
在 Elixir 中,模块并不像 objects/classes 在面向对象的编程语言中那样存储状态。模块属性更像是一个常量,一旦模块被编译,它的值就不能改变,并且除非通过函数公开,否则不能从外部访问。
Pawel 提供了一些关于如何实现类似行为的好方法。这是另一个更简单的方法,因为您不需要在另一个进程中存储状态。
defmodule Adder do
@num_to_add 10
def addTo(input, num_to_add \ @num_to_add),
do: input + num_to_add
end
在上述方法中,我们只是将模块属性设置为默认值。如果我们想覆盖它,只需提供第二个参数。
是否可以实现以下行为,其中一个人试图更改模块属性的值以改变模块方法的行为?
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
抛出以下错误
** (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
如果这不可能(因为 Elixir 中的所有内容都应该是不可变的),是否有任何 Elixir 方法可以实现类似的行为。
这是不可能的,因为属性只存在于该特定模块的编译之前。当模块被编译时,所有属性都被内联并被遗忘,所以当你能够从该模块调用函数时,不再可能修改属性。
此代码应该更清楚地显示这一点:
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
请注意,您可以在模块尚未编译时(例如在模块的主体中)更改属性的值,只需再次设置它即可,就像我在此处将 @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
Elixir 中的一个好的经验法则是,无论何时您需要跟踪某些可变状态,您都需要有一个包装该状态的进程。
在 Elixir 中,模块并不像 objects/classes 在面向对象的编程语言中那样存储状态。模块属性更像是一个常量,一旦模块被编译,它的值就不能改变,并且除非通过函数公开,否则不能从外部访问。
Pawel 提供了一些关于如何实现类似行为的好方法。这是另一个更简单的方法,因为您不需要在另一个进程中存储状态。
defmodule Adder do
@num_to_add 10
def addTo(input, num_to_add \ @num_to_add),
do: input + num_to_add
end
在上述方法中,我们只是将模块属性设置为默认值。如果我们想覆盖它,只需提供第二个参数。