如何重构一个替换参数模式匹配的函数

时间:2017-07-15 10:16:13

标签: elixir

我有这个功能:

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

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

2 个答案:

答案 0 :(得分:6)

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

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

或者,您可以使用模式%module{}和警卫接受列入白名单的结构集:

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

编辑:已更新为使用ŁukaszNiemier在评论中建议的新%module{}模式!

答案 1 :(得分: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

希望这可能有所帮助。