虽然@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
希望这会有所帮助。
为什么不从模式中删除'%Evento {} ='?如果你只想接受结构体,那么'def update(%{__ struct__:_} = struct,attrs,dataSchema)会执行''。还是我误解了一些东西? – Dogbert
@Dogbert你说得对。我只想接受结构。你能把这个变成一个答案,以便我可以接受它吗? –