在 Elixir 中实现行为的所有模块列表

问题描述 投票:0回答:4

获取实现某个行为的所有模块的列表的最短方法是什么?

例如

defmodule X do
  @callback foo()
end

defmodule A do
  @behaviour X
  def foo() do
    "hello"
  end
end

defmodule B do
  @behaviour X
  def foo() do
    "world"
  end
end

我想要一份

[A,B]
的列表。我尝试加载所有模块,然后使用关键属性
behaviour
进行过滤,但我认为这会非常慢。还有其他办法吗?

elixir
4个回答
11
投票

不,没有更好的办法了。行为不会以任何方式索引,因为它们是编译时功能,用于检查是否已定义所有必需的回调函数。所以像这样的东西就已经是最好的了:

for {module, _} <- :code.all_loaded(),
    X in (module.module_info(:attributes)[:behaviour] || []) do
        module
end

4
投票

继续@legoscia 提供的解决方案。 在某些情况下,您可能会为每个模块实现 1 个以上的行为,在这种情况下,如下所示:

for {module, _} <- :code.all_loaded(),
    __MODULE__ in (module.module_info(:attributes)
                   |> Keyword.get_values(:behaviour)
                   |> List.flatten()) do
  module
end

应该使用,因为关键字列表可能有重复的键,直接访问它们不会返回所需的结果。

谢谢@legoscia!


2
投票

我找到了另一种方法,因为

:code.all_loaded()
不适合我。相反,我使用更具体的
:application.get_key/2

case :application.get_key(:my_app, :modules) do
      {:ok, modules} ->
        modules
        |> Enum.filter(fn module -> (module.module_info(:attributes)[:behaviour] || []) |> Enum.member?(MyBehaviour) end)

      error -> {:error, error}
    end

这对我有用,希望对其他人也有帮助。


0
投票

另一种方法是直接从

.beam
文件中提取模块属性。

defmodule BehaviourMapping do
  @behaviour_mapping_key __MODULE__
  @mapping_not_found :not_found

  def recompute_mapping do
    behaviours_by_module =
      for {_, beam_file, _} <- :code.all_available(), reduce: %{} do
        mapping ->
          case :beam_lib.chunks(beam_file, [:attributes]) do
            {:ok, {module, [attributes: attrs]}} ->
              behaviours =
                attrs
                |> Keyword.get_values(:behaviour)
                |> List.flatten()

              Map.put(mapping, module, behaviours)

            {:error, :beam_lib, _reason} ->
              mapping
          end
      end

    result =
      for {module, behaviours} <- behaviours_by_module,
          behaviour <- behaviours,
          reduce: %{} do
        mapping ->
          Map.update(
            mapping,
            behaviour,
            MapSet.new([module]),
            &MapSet.put(&1, module)
          )
      end

    :persistent_term.put(@behaviour_mapping_key, result)
    result
  end

  def mapping do
    case :persistent_term.get(@behaviour_mapping_key, @mapping_not_found) do
      @mapping_not_found ->
        recompute_mapping()

      result ->
        result
    end
  end
end

函数

BehaviourMapping.recompute_mapping/0
返回一个映射,其中键是行为模块,值是实现此行为的模块的MapSets。它还使用 persistent_term 来缓存计算结果。

函数

BehaviourMapping.mapping/0
使用尝试从持久项中获取结果或重新计算映射。

© www.soinside.com 2019 - 2024. All rights reserved.