如何在 Elixir 中规范具有可变参数数量的回调



我有一个包装任何函数的行为。

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

最新更新