2017-07-15 71 views
1

我有这样的功能:如何重构一个函数替换参数模式匹配

def update(%Evento{} = evento, attrs, dataSchema) do 
    evento 
    |> dataSchema.changeset(attrs) 
    |> Repo.update() 
end 

被绑定到%Evento{}结构。
我想使它独立于结构并传递一个参数,以便在调用函数时我可以通过%Evento{}%News{}%Contact{}或任何我想要的结构,同时保持相同的功能/模式匹配检查。

+1

为什么不从模式中删除'%Evento {} ='?如果你只想接受结构体,那么'def update(%{__ struct__:_} = struct,attrs,dataSchema)会执行''。还是我误解了一些东西? – Dogbert

+0

@Dogbert你说得对。我只想接受结构。你能把这个变成一个答案,以便我可以接受它吗? –

回答

6

您可以使用模式%_{}接受任何结构作为参数:

def update(%module{} = struct, attrs, dataSchema) when module in [Evento, Foo, Bar] do 
    ... 
end 

编辑:

def update(%_{} = struct, attrs, dataSchema) do 
    ... 
end 

另外,也可以使用模式%module{}和保护接受白名单设置结构的:更新为使用ŁukaszNiemier建议的新的%module{}模式进行评论!

+1

我个人认为'%module {}'语法比'%{__ struct__:module}'方便得多。 – Hauleth

+0

@ŁukaszNiemier谢谢,我忘了那些最近加入了Elixir!我已经更新了答案。 – Dogbert

1

虽然@Dogbert的答案是[照常]完美和自我解释,我会把这里有点更麻烦的办法,允许为不同类型的输入模块回调,仍然为100%干:

defmodule DryStructMatch do 
    defmacrop clause!(name, mod, fun) do 
    quote bind_quoted: [name: name, mod: mod, fun: fun] do 
     ast = {:%, [], [{:__aliases__, [alias: false], [mod]}, {:%{}, [], []}]} 
     quote do 
     def unquote(name)(unquote(ast) = struct, _arg1, _arg2) do 
      result = struct # |> ... COMMON BLOCK 
      unquote(:"#{name}_callback")(unquote(fun), result) 
     end 
     end 
    end 
    end 

    @doc ~S""" 
    Usage: 

     use DryStructMatch, update: [Foo, Bar: &IO.inspect/1] 

    The above will be expanded into two `update` clauses, the former having 
    no callback, and the latter having a callback that spits the result 
    out to console before returning it (due to `&IO.inspect/1`.) 
    """ 
    defmacro __using__(opts) do 
    Enum.flat_map(opts, fn {name, mods} -> 
     [ 
     quote do 
      defp unquote(:"#{name}_callback")(fun, result) 
      when is_function(fun, 1), do: fun.(result) 
      defp unquote(:"#{name}_callback")(_, result), do: result 

      def unquote(name)(struct, _arg1 \\ nil, _arg2 \\ nil) 
     end | 

     Enum.map(mods, fn 
      {mod, fun} -> clause!(name, mod, fun) 
      mod -> clause!(name, mod, nil) 
     end) 
     ] 
    end) 
    end 
end 

我们在这里做的是:我们声明了参数中指定的很多子句以调用__using__(opts)宏。为了简单起见,这个例子不允许传递普通的block(它是硬编码的),但是很容易修改接受不同公共块的代码。

测试一下:

defmodule Foo, do: defstruct foo: 42 
defmodule Bar, do: defstruct bar: 42 
defmodule Baz, do: defstruct baz: 42 

defmodule A do 
    use DryStructMatch, update: [Foo, Bar: &IO.inspect/1] 
end 

defmodule Test do 
    def test do 
    IO.inspect A.update(%Foo{}) # prints %Foo{foo: 42} (explicit) 
    A.update(%Bar{})   # prints %Bar{bar: 42} (callback) 
    A.update(%Baz{})   # raises `FunctionClauseError` 
    end 
end 

Test.test 

后者将成功调用update第一和第二线Test.test/0,失败的第三个:

%Foo{foo: 42} 
%Bar{bar: 42} 

** (FunctionClauseError) no function clause matching in A.update/3  

    The following arguments were given to A.update/3: 

     # 1 
     %Baz{baz: 42} 

     # 2 
     nil 

     # 3 
     nil 

    iex:17: A.update/3 

希望这会有所帮助。