有没有办法在 Elixir 中将函数体作为字符串获取?
Is there a way to get function body as a string in Elixir?
假设我有一个如下所示的函数定义:
def my_function(argument) do
do_something(argument)+2
end
现在,我期望的输出是这样的:
>> function_body(&my_function/1)
"do_something(argument)+2"
有什么办法可以实现吗?
我认为如果你发布了一个版本,那么源就不再存在了,但是在 test
环境中,你可以提取给定函数的 AST,如下所示:
def fetch_ast(module, fun) do
module.__info__(:compile)[:source]
|> to_string()
|> File.read!()
|> Code.string_to_quoted!()
|> Macro.prewalk(fn
result = {:def, _, [{^fun, _, _} | _]} -> throw(result)
other -> other
end)
:error
catch
result -> {:ok, result}
end
这基本上会再次编译给定模块的文件并找到创建特定函数的 def
。请注意,这里仍然有一些注意事项(在我的代码中被忽略),例如处理具有多个子句或具有不同参数的版本的函数。然而,鉴于函数的结果,您可以再次探索 AST 以检查是否有对您感兴趣的函数的调用,或者将其转换为字符串并查看字符串(更 hacky,但绝对更简单):
{:ok, ast} = fetch_ast(Jason, :encode)
definition = Macro.to_string(ast)
String.contains?(definition, "iodata_to_binary") # => true
虽然 Paweł Obrok 的回答回答了您的问题并满足了您的好奇心(“Elixir 有许多用于元编程的工具,因此我想使用它们。”),但我怀疑您的问题是 XY Question。您已经在评论中澄清:
I want to check if certain function is called inside another, without modifying it […] It's for testing purposes.
但是,我怀疑这还不够。与其测试实现细节(函数 A 调用函数 B),一般来说最好测试函数 A 的实际 行为。这意味着根据输入和输出进行测试,而不是副作用或实施细节。你应该能够完全改变函数的实现,只要输出相同,你的测试应该仍然通过。
类似于:
test "adds two to result of do_something/0" do
assert my_function() == do_something() + 2
end
但更好的办法是更改 my_function/0
的设计,使其接受一个参数,不执行副作用,并且可以链接:
do_something() |> my_function()
那么测试可以简单很多:
test "adds two" do
assert my_function(1) == 3
end
假设我有一个如下所示的函数定义:
def my_function(argument) do
do_something(argument)+2
end
现在,我期望的输出是这样的:
>> function_body(&my_function/1)
"do_something(argument)+2"
有什么办法可以实现吗?
我认为如果你发布了一个版本,那么源就不再存在了,但是在 test
环境中,你可以提取给定函数的 AST,如下所示:
def fetch_ast(module, fun) do
module.__info__(:compile)[:source]
|> to_string()
|> File.read!()
|> Code.string_to_quoted!()
|> Macro.prewalk(fn
result = {:def, _, [{^fun, _, _} | _]} -> throw(result)
other -> other
end)
:error
catch
result -> {:ok, result}
end
这基本上会再次编译给定模块的文件并找到创建特定函数的 def
。请注意,这里仍然有一些注意事项(在我的代码中被忽略),例如处理具有多个子句或具有不同参数的版本的函数。然而,鉴于函数的结果,您可以再次探索 AST 以检查是否有对您感兴趣的函数的调用,或者将其转换为字符串并查看字符串(更 hacky,但绝对更简单):
{:ok, ast} = fetch_ast(Jason, :encode)
definition = Macro.to_string(ast)
String.contains?(definition, "iodata_to_binary") # => true
虽然 Paweł Obrok 的回答回答了您的问题并满足了您的好奇心(“Elixir 有许多用于元编程的工具,因此我想使用它们。”),但我怀疑您的问题是 XY Question。您已经在评论中澄清:
I want to check if certain function is called inside another, without modifying it […] It's for testing purposes.
但是,我怀疑这还不够。与其测试实现细节(函数 A 调用函数 B),一般来说最好测试函数 A 的实际 行为。这意味着根据输入和输出进行测试,而不是副作用或实施细节。你应该能够完全改变函数的实现,只要输出相同,你的测试应该仍然通过。
类似于:
test "adds two to result of do_something/0" do
assert my_function() == do_something() + 2
end
但更好的办法是更改 my_function/0
的设计,使其接受一个参数,不执行副作用,并且可以链接:
do_something() |> my_function()
那么测试可以简单很多:
test "adds two" do
assert my_function(1) == 3
end