代码之家  ›  专栏  ›  技术社区  ›  intentionally-left-nil

带保护子句的内省函数

  •  2
  • intentionally-left-nil  · 技术社区  · 6 年前

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

    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) 不起作用,因为它只会返回 [greet: 1]

    2 回复  |  直到 6 年前
        1
  •  2
  •   Dogbert    6 年前

    你可以将一个模块的代码分解成“抽象代码”,然后通过挖掘得到这个信息。下面是如何获取模块中每个函数的子句:

    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上。

        2
  •  1
  •   Aleksei Matiushkin    6 年前

    如果模块是第三方并且/或者已经编译,请参考@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存储信息,因为它存储在 汇编 阶段和典型用法将在运行时使用。