如何扩展/继承elixir模块?
假设长生不老药库定义:如何扩展/继承elixir模块?,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" =&g
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
我想定义一个模块MyDecoder
,它只需向上述模块添加一个def decode
。在oo语言中,这将通过某种继承/混合/扩展来完成
如何在elixir中做到这一点?如果您不控制原始模块,我不确定是否有一个简单的解决方案。也许您可以尝试递归地预处理数据,然后将结果提供给原始实现 但是,如果您可以控制原始模块,一种方法是将公共子句提取到宏中,然后在实际的解码器模块中使用:
defmodule Decoder.Common do
defmacro __using__(_) do
quote 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
end
end
defmodule Decoder do
use Decoder.Common
end
defmodule MyDecoder do
def decode(%{"FOO" => value}), do: "BAR"
use Decoder.Common
end
有一种机制可以扩展模块的行为。这叫做协议。你可以找到更多的信息。您可以认为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
在别的地方我会有T.is\u valid\u value?(myvalue,V.is\u odd?
。突然,我的客户意识到,他们需要检查值是否大于100,而不是检查值是否为奇数。所以我会按照这些思路做一些事情:
defmodule V do
def greater_than_100?(v) do
v > 100
end
end
然后我将我的调用更改为:T.is\u valid\u value?(myvalue,V.大于\u 100?
注:我故意让代码非常简单,以表明我的观点。这可能不是有效的语法。我还没检查过,现在不行
就这样。这就是全部。智能开发人员可能会不同意,但对我来说,这比继承行为并重写它要简单得多。显然,你可以。看看哪种方法使用了一些相当“模糊”的方法来列出模块的公共函数,然后从中生成委托。很酷 这就是它的全部内容:
defmodule Extension do
defmacro扩展(模块)do
module=Macro.expand(模块,调用方)
函数=模块信息(:函数)
签名=Enum.map函数,fn{name,arity}->
args=如果arity==0 do
[]
其他的
Enum.map 1。。arity,fn(i)->
{binary_to_原子(>),[],nil}
结束
结束
{name,[],args}
结束
引述
defdelegate取消报价(签名),收件人:取消报价(模块)
除雾器(功能)
结束
结束
结束
您可以这样使用它:
defmodule MyModule do
需要延期
Extension.extenses父模块
# ...
结束
不幸的是,它对最近的Elixir版本发出了警告,但我相信这是可以解决的。除此之外,它就像一个符咒
编辑以避免引发警告:
defmodule Extension do
defmacro扩展(模块)do
module=Macro.expand(模块,调用方)
函数=模块信息(:函数)
签名=Enum.map函数,fn{name,arity}->
args=如果arity==0 do
[]
其他的
Enum.map 1。。arity,fn(i)->
{String.to_atom(>),[],nil}
结束
结束
{name,[],args}
结束
zipped=List.zip([签名、函数])
对于sig_func来说,也许defdelegate
会起到以下作用:
defmodule MyDecoder do
def decode(%{"X" => value}), do: value
defdelegate decode(map), to: Decoder
end
这不起作用,因为Decoder.decode(x)将无法调用MyDecoder.decode,因为它通过递归/委派工作。例如,我需要添加def decode(%%{“NULL”=>true}),do:nil
到ExAws.Dynamo.Decoder
这是实现中的一个错误吗?如果是这样的话,做一个公关,使用你自己的叉子,直到修复程序发布为止,怎么样?这样做。我想知道长生不老药的通解是什么样子的。据我所知,没有一个干净的方式来扩展一个模块,只需一个函数而不重写原来的库。您无法控制整个源代码。OO用继承解决的大多数其他问题实际上都可以用长生不老药优雅地解决。也就是说,即使OO也不允许你添加一个条款,就像你在这里试图做的那样,或者我弄错了吗?我认为最初的问题指的是一个外部库,他不想碰它。如果他指的是一个外部库,那么他的问题就不清楚了。