有没有办法在 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