你如何扩展/继承elixir模块?

ans*_*hul 7 elixir

假设一个elixir库定义:

defmodule Decoder do

  def decode(%{"BOOL" => true}),    do: true
  def decode(%{"BOOL" => false}),   do: false
  def decode(%{"BOOL" => "true"}),  do: true
  def decode(%{"BOOL" => "false"}), do: false
  def decode(%{"B" => value}),      do: value
  def decode(%{"S" => value}),      do: value
  def decode(%{"M" => value}),      do: value |> decode
  def decode(item = %{}) do
    item |> Enum.reduce(%{}, fn({k, v}, map) ->
      Map.put(map, k, decode(v))
    end)
  end
end
Run Code Online (Sandbox Code Playgroud)

我想定义一个模块MyDecoder,它只是def decode向上面的模块添加一个.在oo语言中,这将通过某种继承/ mixin/extends来完成.

我如何在长生不老药中做到这一点?

Ono*_*cci 19

有一种机制可以扩展模块的行为.它被称为协议.您可以在此处找到更多信息.您可以将Elixir协议视为类似于OO中的接口.

但是,在这种特殊情况下,它就像是用大锤拍打苍蝇.我的意思是你可能会重写代码以使用协议但是如果你想简单地扩展解析器然后分叉代码并进行修改.哦,不要忘记将PR发送回原始开发人员,因为他可能也希望得到您的修复.

有时最简单的答案是最好的答案.即使这是OO代码,如果某些开发人员继承了类或类似的东西,我会在代码审查中标记它.为什么?因为遗传导致病理代码耦合.

通常在FP中(注意我在这里做了很大的概括)我们通常扩展行为的方式是通过高阶函数.也就是说,如果我们想要不同的行为,我们就不会使用多态性; 我们只是直接将我们想要的行为传递给更高阶的函数.当我说"传递行为"时,我的意思是什么?考虑我有一些验证代码,例如:

defmodule V do
  def is_odd?(v) do
    rem(v,2) != 0
  end
end

defmodule T do
   def is_valid_value?(v, f) do
     if f(v), do: true, else: false
   end
end
Run Code Online (Sandbox Code Playgroud)

而在其他地方,我会有T.is_valid_value?(myvalue, V.is_odd?).突然间,我的客户意识到,不是检查值是否奇怪,而是需要检查它是否大于100.所以我会沿着这些方向做一些事情:

defmodule V do
  def greater_than_100?(v) do
    v > 100
  end
end
Run Code Online (Sandbox Code Playgroud)

然后我会改变我的呼吁: T.is_valid_value?(myvalue, V.greater_than_100?)

注意:我故意保持代码非常简单,以说明问题.这可能不是有效的语法.我没有检查,我现在不能.

而已.就这样.智能开发人员可以不同意,但对我而言,这比继承行为和覆盖行为更直接,更容易理解.


Dec*_*lan 7

也许defdelegate可以解决问题:

defmodule MyDecoder do
  def decode(%{"X" => value}), do: value

  defdelegate decode(map), to: Decoder
end
Run Code Online (Sandbox Code Playgroud)


sam*_*mvv 6

显然可以。看看这个要点,它使用一些相当“模糊”的方法来列出模块的公共功能,然后从中生成委托。它太酷了。

这就是全部内容:

defmodule Extension do
  defmacro extends(module) do
    module = Macro.expand(module, __CALLER__)
    functions = module.__info__(:functions)
    signatures = Enum.map functions, fn { name, arity } ->
      args = if arity == 0 do
               []
             else
               Enum.map 1 .. arity, fn(i) ->
                 { binary_to_atom(<< ?x, ?A + i - 1 >>), [], nil }
               end
             end
      { name, [], args }
    end
    quote do
      defdelegate unquote(signatures), to: unquote(module)
      defoverridable unquote(functions)
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

您可以这样使用它:

defmodule MyModule do
   require Extension
   Extension.extends ParentModule
   # ...
end
Run Code Online (Sandbox Code Playgroud)

不幸的是,它对最新的Elixir版本提出了警告,但我相信可以解决。除此之外,它就像一个魅力!

编辑以免引发警告:

defmodule Extension do
  defmacro extends(module) do
    module = Macro.expand(module, __CALLER__)
    functions = module.__info__(:functions)
    signatures = Enum.map functions, fn { name, arity } ->
      args = if arity == 0 do
               []
             else
               Enum.map 1 .. arity, fn(i) ->
                 { String.to_atom(<< ?x, ?A + i - 1 >>), [], nil }
               end
             end
      { name, [], args }
    end

    zipped = List.zip([signatures, functions])
    for sig_func <- zipped do
      quote do
        defdelegate unquote(elem(sig_func, 0)), to: unquote(module)
        defoverridable unquote([elem(sig_func, 1)])
      end
    end
  end
end
Run Code Online (Sandbox Code Playgroud)