2

我有一个包含任何功能的行为。

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.

是否可以在不重新实现行为的情况下正确指定这一点?

4

1 回答 1

2

我不确定我是否正确理解了这个问题;我不遵循像我们在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
于 2019-08-29T05:03:23.673 回答