我有一个包装任何函数的行为。
defmodule MyBehaviour do
@callback do_run( ? ) :: ? #the ? means I don't know what goes here
defmacro __using__(_) do
quote location: :keep do
@behaviour MyBehaviour
def run, do: MyBehaviour.run(__MODULE__, [])
def run(do_run_args), do: MyBehaviour.run(__MODULE__, do_run_args)
end
end
def run(module, do_run_args) do
do_run_fn = fn ->
apply(module, :do_run, do_run_args)
end
# execute do_run in a transaction, with some other goodies
end
end
defmodule Implementation do
use MyBehaviour
def do_run(arg1), do: :ok
end
Implemenation.run([:arg1])
这个想法是,通过实现MyBehaviour
,模块Implementation
将具有run([:arg1])
将调用do_run(:arg1)
的函数。
如何为具有可变参数数的函数编写@callback
规范?
我以为@callback do_run(...) :: any()
会起作用,但透析器给了我一个Undefined callback function do_run/1
的错误,所以我假设...
意味着任何参数,而不是零参数。
实际上,我只有两种情况:零和一参数。 我想过像这样重载规范:
@callback do_run() :: any()
@callback do_run(any()) :: any()
但这需要两个do_run
函数,因为相同的名称和不同的 arity 在 Erlang 世界中是两个独立的函数。
如果我把它弄@optional_callback
那么它们都有可能都不会得到实施。
@type
允许指定像这样的任何 arity(... -> any())
函数,所以我想应该可以用@callback
做同样的事情。
是否可以在不重新实现行为的情况下正确规范这一点?
我不确定我是否正确理解了这个问题;我不遵循像我们在MFA中那样总是传递参数列表会有什么问题。
无论如何,对于上述问题,Module.__after_compile__/2
回调和@optional_callbacks
是您的朋友。
defmodule MyBehaviour do
@callback do_run() :: :ok
@callback do_run(args :: any()) :: :ok
@optional_callbacks do_run: 0, do_run: 1
defmacro __using__(_) do
quote location: :keep do
@behaviour MyBehaviour
@after_compile MyBehaviour
def run(), do: MyBehaviour.run(__MODULE__)
def run(do_run_args), do: MyBehaviour.run(__MODULE__, do_run_args)
end
end
def run(module),
do: fn -> apply(module, :do_run, []) end
def run(module, do_run_args),
do: fn -> apply(module, :do_run, do_run_args) end
def __after_compile__(env, _bytecode) do
:functions
|> env.module.__info__()
|> Keyword.get_values(:do_run)
|> case do
[] -> raise "One of `do_run/0` _or_ `do_run/1` is required"
[0] -> :ok # without args
[1] -> :ok # with args
[_] -> raise "Arity `0` _or_ `1` please"
[_|_] -> raise "Either `do_run/0` _or_ `do_run/1` please"
end
end
end
并将其用作:
defmodule Ok0 do
use MyBehaviour
def do_run(), do: :ok
end
Ok0.run()
defmodule Ok1 do
use MyBehaviour
def do_run(arg1), do: :ok
end
Ok1.run([:arg1])
defmodule KoNone do
use MyBehaviour
end
#⇒ ** (RuntimeError) One of `do_run/0` _or_ `do_run/1` is required
defmodule KoBoth do
use MyBehaviour
def do_run(), do: :ok
def do_run(arg1), do: :ok
end
#⇒ ** (RuntimeError) Either `do_run/0` _or_ `do_run/1` please
defmodule KoArity do
use MyBehaviour
def do_run(arg1, arg2), do: :ok
end
#⇒ ** (RuntimeError) Arity `0` _or_ `1` please