首先,真正的问题:我注意到由于拼写错误或重新命名的原子而导致我的代码库出现运行时错误的常见原因,例如:
defmodule SomeGenServer do
def delayed_action do
Process.send_after(self, :delayed_action, 5_000)
end
#...snip...
def handle_info(:delayed_acton, state), do: handle_delayed_action()
end
此错误(:delayed_acton
与:delayed_action
)仅在运行时被发现,此时调用delayed_action/0
并且意外消息在5秒后崩溃GenServer。我觉得这是编译器应该能够帮助我在编译时找到的问题,所以我正在寻找创建这种修复的工具。
现在,问题是:如何确定模块中使用的原子?我查看了__ENV__
以及Module
和Code
模块中的函数,我找不到我要查找的内容,除非有一种可预测的方法来解析__after_compile__/2
回调中的字节码?
这是我最终想要创建的概念语法示例,但我无法在SomeGenServer
defmodule SomeGenServer do
use AtomEnforcer, atoms: [:delayed_action]
# This line would cause a compilation error:
def handle_info(:delayed_acton, state), do: handle_delayed_action()
end
最后,我对其他人如何处理这个问题的答案持开放态度,无论是通过短语,模块属性还是其他一些聪明。
答案 0 :(得分:3)
您还可以将atom定义为模块属性,并在任何地方使用模块属性。
我知道这不会阻止编译,但至少你得到一个很好的警告。
此外,当您需要从外部模块访问此atom时,请使用如下函数:
@delayed_action :delayed_action
def delayed_action_name, do: @delayed_action
如果您经常使用它,最好从列表中编写一个为您定义属性和函数的宏,例如:
use AtomDefiner, atoms: [:delayed_action, :another_action]
并在需要原子时使用@delayed_action
。
答案 1 :(得分:1)
经过一些搜索和R& D,我发现Erlang标准库中的:beam_lib.chunks/2
能够解析字节码并从编译的.beam中提取一组原子:
iex> defmodule Test do
...> @after_compile __MODULE__
...> def __after_compile__(_env, bytecode) do
...> IO.inspect(:beam_lib.chunks(bytecode, [:atoms]))
...> end
...>
...> def test_method do
...> :return_value
...> end
...> end
{:ok,
{Test,
[
atoms: [
{1, Test},
{2, :__info__},
{3, :functions},
{4, :md5},
{5, :compile},
{6, :attributes},
{7, :module},
{8, :macros},
{9, :deprecated},
{10, :erlang},
{11, :get_module_info},
{12, :__after_compile__},
{13, :beam_lib},
{14, :chunks},
{15, IO},
{16, :inspect},
{17, :test_method},
{18, :return_value},
{19, :module_info}
]
]}}
有了这个,我能够用我想要的功能拼凑一个宏。有兴趣的人可以找到它here,虽然我不相信这是一个比@vfsoraki提供的更好的解决方案。
答案 2 :(得分:-2)
人们通过测试来解决这个问题。
由于很多原因,没有方便的方法来获取特定模块中声明的原子列表,包括但不限于:
String.to_atom
:
Process.send_after(self, String.to_atom("delayed_action"), 5_000)
; Process.send_after(self, :"delayed_action"), 5_000)
; List.to_atom('delayed_action')
; 此外,您尝试实现的目标通常会打破整个OTP在进程之间自由传递消息的想法。您的进程可能从任何地方接收消息,并且将任何内容限制在此特定模块中声明的原子列表与OTP的整个概念非常相反。
有一种方法可以确保使用String.to_existing_atom/1
和/或List.to_existing_atom/1
不会过多地污染原子表,但两者仍然是运行时。
如果您仍然相信,您希望滥用集成/单元测试而不是将OTP限制为仅允许在同一模块中定义的原子,请在Module@after_compile
挂钩中解析AST并自己收集原子列表
此外,可以从允许的原子和签名列表中有效地生成像def handle_info(:delayed_acton, state), do: handle_delayed_action()
这样的函数列表。请不要告诉任何人我建议采用这种方法:)
您可能还想引用this answer来了解此任务对OTP的不自然程度。