2016-02-09 24 views
3

假設仙丹庫定義:如何擴展/繼承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 

我想定義一個模塊MyDecoder剛剛增添了一分def decode上述模塊。在oo語言中,這可以通過某種繼承/混搭/擴展來完成。

我該如何在靈藥中做到這一點?

+0

例如,我需要在https://github.com/CargoSense/ex_aws/上添加def decode(%{「NULL」=> true}),do:nil'到'ExAws.Dynamo.Decoder' blob/c8d62612a427ef5cf6cfd74b772dcf82e90ab567/lib/ex_aws/dynamo/decoder.ex – anshul

+1

這是實施中的錯誤嗎?如果是這樣,那麼在公佈修補程序之前如何製作公關並使用自己的分支? –

+0

這樣做。想知道elixir的一般解決方案是什麼樣子。據我所知,沒有一種乾淨的方式來擴展一個模塊只有一個功能,而不需要重寫原始庫。 – anshul

回答

4

顯然,你可以。看看this gist,它使用一些相當「不明確」的方法來列出模塊的公共職能,然後從中生成委託。它太酷了。

這裏是它的全部:

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 

您可以使用它像這樣:

defmodule MyModule do 
    require Extension 
    Extension.extends ParentModule 
    # ... 
end 

不幸的是,它拋出一個警告,在最近的靈藥建立,但我敢肯定那可以解決。除此之外,它就像一個魅力!

1

如果您不控制原始模塊,我不確定是否有直接的解決方案。也許你可以嘗試遞歸地預處理數據,然後將結果提供給原始實現。

如果你能控制但原來的模塊,這樣做的一個方法是通過提取通用條款成宏,然後在實際的解碼器模塊使用此:

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 
+0

這不起作用,因爲Decoder.decode(x)將無法調用MyDecoder.decode,因爲它是通過遞歸/委派進行的。 – anshul

14

有一種機制來擴展一個模塊的行爲。它被稱爲協議。你可以找到更多的信息here。您可以將Elixir協議看作類似於OO中的接口。

但是,在這種特殊情況下,就像用大錘撲蒼蠅。我的意思是你可以重寫代碼來使用協議,但如果你想簡單地擴展解析器,然後分叉代碼並進行修改。哦,不要忘了將PR發回給原始開發者,因爲他可能也想要修復。

有時最簡單的答案是最好的答案。即使這是OO代碼,如果某些開發人員繼承了該類或類似的東西,我會在代碼審查中將其標記出來。爲什麼?因爲遺傳導致病理code coupling

一般來說,在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_valid_value?(myvalue, V.is_odd?)。突然間,我的客戶意識到,而不是檢查,如果值是奇數,他們需要檢查它是否大於100。所以,我會做這些方針的東西:

defmodule V do 
    def greater_than_100?(v) do 
    v > 100 
    end 
end 

然後,我將我的電話改成這樣: T.is_valid_value?(myvalue, V.greater_than_100?)

注:我故意保持代碼非常簡單,以表明觀點。這可能不是有效的語法。我沒有檢查過,現在也不行。

就是這樣。就這樣。智能開發人員可能不同意,但對我而言,這比繼承行爲和覆蓋行爲更直接,更容易遵循。

+0

我認爲原來的問題提到了一個他不想碰的外部圖書館。 – raarts

+0

如果他指的是外部圖書館,他的問題並不清楚。 –