如何在 Elixir 中使用可变数量的参数指定回调
How to spec a callback with variable number of arguments in 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()
会起作用,但 Dialyzer 给我一个错误 Undefined callback function do_run/1
,所以我假设 ...
表示任何参数但不是零参数。
实际上,我只有两种情况:零和一个 arg。
我考虑过像这样重载规范:
@callback do_run() :: any()
@callback do_run(any()) :: any()
但这需要两个 do_run
函数,因为在 Erlang 世界中相同的名称和不同的元数是两个独立的函数。
如果我做到了 @optional_callback
有可能两者都不会实施。
@type
允许像这样指定任何参数的函数 (... -> any())
所以我想应该可以用 @callback
.
做同样的事情
是否可以在不重新实现行为的情况下正确规范此行为?
我不确定我是否正确理解了问题;我不理解总是传递 a list 参数会有什么问题,就像我们在 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
我有一个包装任何函数的行为。
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()
会起作用,但 Dialyzer 给我一个错误 Undefined callback function do_run/1
,所以我假设 ...
表示任何参数但不是零参数。
实际上,我只有两种情况:零和一个 arg。 我考虑过像这样重载规范:
@callback do_run() :: any()
@callback do_run(any()) :: any()
但这需要两个 do_run
函数,因为在 Erlang 世界中相同的名称和不同的元数是两个独立的函数。
如果我做到了 @optional_callback
有可能两者都不会实施。
@type
允许像这样指定任何参数的函数 (... -> any())
所以我想应该可以用 @callback
.
是否可以在不重新实现行为的情况下正确规范此行为?
我不确定我是否正确理解了问题;我不理解总是传递 a list 参数会有什么问题,就像我们在 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