具有保护条款的内省功能

时间:2018-05-22 22:42:26

标签: elixir introspection guard-clause

假设一个模块具有两个具有相同arity但具有不同保护子句的函数,我如何(理想情况下)看到这些子句是什么,或者至少有两个函数?

var short = textEllipsis('a very long text', 10);
console.log(short);
// "a very ..."

var short = textEllipsis('a very long text', 10, { side: 'start' });
console.log(short);
// "...ng text"

var short = textEllipsis('a very long text', 10, { textEllipsis: ' END' });
console.log(short);
// "a very END"

defmodule Test do def greet(name) when name == "foo" do IO.puts("Hello, bar") end def greet(name), do: IO.puts("Hello, #{name}") end 不起作用,因为它只返回Test.__info__(:functions)

2 个答案:

答案 0 :(得分:2)

您可以将模块的代码反编译为“抽象代码”并深入研究以获取此信息。以下是如何获取模块中每个函数的子句:

module = Test

{:ok, {^module, [abstract_code: {:raw_abstract_v1, abstract_code}]}} = :beam_lib.chunks(module, [:abstract_code])

for {:function, _, name, arity, clauses} <- abstract_code do
  # Uncomment the next line to print the AST of the clauses.
  # IO.inspect(clauses)
  IO.inspect {name, arity, length(clauses)}
end

输出:

{:__info__, 1, 7}
{:greet, 1, 2}

注意:这可能是私有API,可能会在将来的Erlang / OTP版本中发生变化。上面的输出是在Erlang / OTP 20上。

答案 1 :(得分:1)

如果模块是第三方和/或已编译,请参阅@Dogbert提供的答案。

如果该模块是拥有的,则可以使用@on_definition钩子在编译阶段收集所请求的信息:

defmodule TestInfo do
  def on_definition(_env, kind, name, args, guards, body) do
    with {:ok, table} <- :dets.open_file(:test_info, type: :set) do
      clauses =
        case :dets.lookup(table, name) do
          {:error, _reason} -> []
          [] -> []
          list when is_list(list) -> list[name]
        end

      :dets.insert(table, {name, [{kind, args, guards} | clauses]})
      :dets.close(table)
    end
  end
end

defmodule Test do
  @on_definition {TestInfo, :on_definition} # ⇐ THIS

  def greet(name) when name == "foo" do
    IO.puts("Hello, bar")
  end

  def greet(name), do: IO.puts("Hello, #{name}")
end

现在,您已将所有定义存储在DETS中:

{:ok, table} = :dets.open_file(:test_info, type: :set) 
:dets.lookup(table, :greet)
#⇒ [
#    greet: [
#      {:def, [{:name, [line: 10], nil}], []},
#      {:def, [{:name, [line: 6], nil}],
#       [{:==, [line: 6], [{:name, [line: 6], nil}, "foo"]}]}]
#  ]

:dets.close(table)

我使用DETS存储信息,因为它存储在编译阶段,典型用法是在运行时。