在 elixir 中使用来自已声明结构的原子适用于 repl 但不适用于应用程序
Using atoms from declared structs in elixir works on repl but not in application
我想在 elixir 中使用 String.to_existing_atom 以避免内存泄漏。
这 100% 适用于 REPL:
iex(1)> defmodule MyModule do
...(1)> defstruct my_crazy_atom: nil
...(1)> end
{:module, MyModule,
<<70, 79, 82, ...>>,
%MyModule{my_crazy_atom: nil}}
所以现在原子 my_crazy_atom
存在了。我可以验证这一点:
iex(2)> String.to_existing_atom "my_crazy_atom"
:my_crazy_atom
相比于:
iex(3)> String.to_existing_atom "my_crazy_atom2"
** (ArgumentError) argument error
:erlang.binary_to_existing_atom("my_crazy_atom2", :utf8)
但是我有一些代码看起来像这样:
defmodule Broadcast.Config.File do
defstruct channel_id: nil, parser: nil
end
从启动 GenServer 进程后的方法调用,
我可以用 Poison 的
解码
keys: :atoms!
甚至直接打电话给
String.to_existing_atom("parser")
在代码的同一个地方,我得到一个错误:
** (Mix) Could not start application broadcast: exited in:
Broadcast.Application.start(:normal, [])
** (EXIT) an exception was raised:
** (ArgumentError) argument error
:erlang.binary_to_existing_atom("parser", :utf8)
奇怪的是,如果我实例化结构并检查它,那么问题就消失了!
IO.puts inspect %Broadcast.Config.File{}
String.to_existing_atom("parser")
这是怎么回事?这是某种订购方式吗?
REPL 和您的应用程序之间的区别在于,在 REPL 中编译过程会立即发生。也就是说,虚拟机,如它所见
iex(1)> defmodule MyModule do
...(1)> defstruct my_crazy_atom: nil
...(1)> end
立即编译。在编译阶段,正在创建原子并且一切正常。
在您的应用程序 OTOH 中,正在通过 VM 的不同调用提前执行编译过程。因此,除非显式使用该结构,否则不会创建原子。
可以将其视为 OOP 中的 class 声明与实例化:class 定义的存在并不能保证存在此 class 的实例。
要查看实际发生的情况和时间,请尝试将 IO.puts "I AM HERE"
放入模块声明中,紧接在 defstruct
之前。在 REPL 中,你会立即看到这一行。在您的应用程序中,您会在编译期间看到它,而在 运行 应用程序正常时不会看到它。
重现步骤:
$ mix new blah && cd blah
$ cat lib/blah.ex
defmodule Blah do
defstruct my_crazy_atom: nil
end
defmodule Foo do
def to_existing_atom, do: String.to_existing_atom("my_crazy_atom")
end
$ mix compile
$ iex -S mix
iex|1 ▶ Foo.to_existing_atom
** (ArgumentError) argument error
:erlang.binary_to_existing_atom("my_crazy_atom", :utf8)
(blah) lib/blah.ex:6: Foo.to_existing_atom/0
iex|1 ▶ %Blah{}
%Blah{my_crazy_atom: nil}
iex|2 ▶ Foo.to_existing_atom
:my_crazy_atom
为了全面起见,我把它放在这里(取自this brilliant answer):
defmodule AtomLookUp do
defp atom_by_number(n),
do: :erlang.binary_to_term(<<131, 75, n::24>>)
def atoms(n \ 0) do
try do
[atom_by_number(n) | atoms(n + 1)]
rescue
_ -> []
end
end
def atom?(value) when is_binary(value) do
result = atoms()
|> Enum.map(&Atom.to_string/1)
|> Enum.find(& &1 == value)
if result, do: String.to_existing_atom(result)
end
end
iex|1 ▶ AtomLookUp.atom? "my_crazy_atom"
nil
iex|2 ▶ %Blah{}
%Blah{my_crazy_atom: nil}
iex|3 ▶ AtomLookUp.atom? "my_crazy_atom"
:my_crazy_atom
发生这种情况是因为 Elixir 默认情况下会在首次使用时从已编译的 .beam 文件中延迟加载模块。 (如果在 mix.exs
中将 start_permanent
设置为 true
,您的代码将起作用,在 :prod
环境中默认设置为 true,因为那时 Elixir 会急切地加载所有模块包裹。)
在下面的代码中,原子 :my_crazy_atom
将出现在 Blah
模块的代码中,但它不会出现在 Foo
中。如果您启动 REPL 会话并且 运行 Foo.to_existing_atom
,Blah
模块 未 加载,这会导致 String.to_existing_atom("my_crazy_atom")
失败。
# Credits: @mudasobwa
defmodule Blah do
defstruct my_crazy_atom: nil
end
defmodule Foo do
def to_existing_atom, do: String.to_existing_atom("my_crazy_atom")
end
如您所见,如果您手动创建结构一次,所有后续对 String.to_existing_atom("my_crazy_atom")
return 的调用都是正确的原子。这是因为当您创建一个结构时,Elixir 将加载该模块的 .beam 文件,该文件还将加载该模块使用的所有原子。
加载模块的更好方法(与创建结构相比)是使用 Code.ensure_loaded/1
加载模块:
{:module, _} = Code.ensure_loaded(Blah)
我想在 elixir 中使用 String.to_existing_atom 以避免内存泄漏。
这 100% 适用于 REPL:
iex(1)> defmodule MyModule do
...(1)> defstruct my_crazy_atom: nil
...(1)> end
{:module, MyModule,
<<70, 79, 82, ...>>,
%MyModule{my_crazy_atom: nil}}
所以现在原子 my_crazy_atom
存在了。我可以验证这一点:
iex(2)> String.to_existing_atom "my_crazy_atom"
:my_crazy_atom
相比于:
iex(3)> String.to_existing_atom "my_crazy_atom2"
** (ArgumentError) argument error
:erlang.binary_to_existing_atom("my_crazy_atom2", :utf8)
但是我有一些代码看起来像这样:
defmodule Broadcast.Config.File do
defstruct channel_id: nil, parser: nil
end
从启动 GenServer 进程后的方法调用, 我可以用 Poison 的
解码keys: :atoms!
甚至直接打电话给
String.to_existing_atom("parser")
在代码的同一个地方,我得到一个错误:
** (Mix) Could not start application broadcast: exited in:
Broadcast.Application.start(:normal, [])
** (EXIT) an exception was raised:
** (ArgumentError) argument error
:erlang.binary_to_existing_atom("parser", :utf8)
奇怪的是,如果我实例化结构并检查它,那么问题就消失了!
IO.puts inspect %Broadcast.Config.File{}
String.to_existing_atom("parser")
这是怎么回事?这是某种订购方式吗?
REPL 和您的应用程序之间的区别在于,在 REPL 中编译过程会立即发生。也就是说,虚拟机,如它所见
iex(1)> defmodule MyModule do
...(1)> defstruct my_crazy_atom: nil
...(1)> end
立即编译。在编译阶段,正在创建原子并且一切正常。
在您的应用程序 OTOH 中,正在通过 VM 的不同调用提前执行编译过程。因此,除非显式使用该结构,否则不会创建原子。
可以将其视为 OOP 中的 class 声明与实例化:class 定义的存在并不能保证存在此 class 的实例。
要查看实际发生的情况和时间,请尝试将 IO.puts "I AM HERE"
放入模块声明中,紧接在 defstruct
之前。在 REPL 中,你会立即看到这一行。在您的应用程序中,您会在编译期间看到它,而在 运行 应用程序正常时不会看到它。
重现步骤:
$ mix new blah && cd blah
$ cat lib/blah.ex
defmodule Blah do
defstruct my_crazy_atom: nil
end
defmodule Foo do
def to_existing_atom, do: String.to_existing_atom("my_crazy_atom")
end
$ mix compile
$ iex -S mix
iex|1 ▶ Foo.to_existing_atom
** (ArgumentError) argument error
:erlang.binary_to_existing_atom("my_crazy_atom", :utf8)
(blah) lib/blah.ex:6: Foo.to_existing_atom/0
iex|1 ▶ %Blah{}
%Blah{my_crazy_atom: nil}
iex|2 ▶ Foo.to_existing_atom
:my_crazy_atom
为了全面起见,我把它放在这里(取自this brilliant answer):
defmodule AtomLookUp do
defp atom_by_number(n),
do: :erlang.binary_to_term(<<131, 75, n::24>>)
def atoms(n \ 0) do
try do
[atom_by_number(n) | atoms(n + 1)]
rescue
_ -> []
end
end
def atom?(value) when is_binary(value) do
result = atoms()
|> Enum.map(&Atom.to_string/1)
|> Enum.find(& &1 == value)
if result, do: String.to_existing_atom(result)
end
end
iex|1 ▶ AtomLookUp.atom? "my_crazy_atom"
nil
iex|2 ▶ %Blah{}
%Blah{my_crazy_atom: nil}
iex|3 ▶ AtomLookUp.atom? "my_crazy_atom"
:my_crazy_atom
发生这种情况是因为 Elixir 默认情况下会在首次使用时从已编译的 .beam 文件中延迟加载模块。 (如果在 mix.exs
中将 start_permanent
设置为 true
,您的代码将起作用,在 :prod
环境中默认设置为 true,因为那时 Elixir 会急切地加载所有模块包裹。)
在下面的代码中,原子 :my_crazy_atom
将出现在 Blah
模块的代码中,但它不会出现在 Foo
中。如果您启动 REPL 会话并且 运行 Foo.to_existing_atom
,Blah
模块 未 加载,这会导致 String.to_existing_atom("my_crazy_atom")
失败。
# Credits: @mudasobwa
defmodule Blah do
defstruct my_crazy_atom: nil
end
defmodule Foo do
def to_existing_atom, do: String.to_existing_atom("my_crazy_atom")
end
如您所见,如果您手动创建结构一次,所有后续对 String.to_existing_atom("my_crazy_atom")
return 的调用都是正确的原子。这是因为当您创建一个结构时,Elixir 将加载该模块的 .beam 文件,该文件还将加载该模块使用的所有原子。
加载模块的更好方法(与创建结构相比)是使用 Code.ensure_loaded/1
加载模块:
{:module, _} = Code.ensure_loaded(Blah)