如何使用typespecs和Dialyzer with Behaviors?

时间:2017-06-24 01:46:55

标签: elixir

在Elixir中,我如何记录函数将返回实现特定行为的模块?

要使用一个简单的例子,假设我创建了一个由两个模块实现的GreeterBehaviour行为:

defmodule GreeterBehaviour do
  @callback say_hello(String.t) :: String.t
end

defmodule FormalGreeter do
  @behaviour GreeterBehaviour

  def say_hello(name) do
    "Good day to you #{name}"
  end
end

defmodule CasualGreeter do
  @behaviour GreeterBehaviour

  def say_hello(name) do
    "Hey #{name}"
  end
end

然后,我希望通过函数检索Greeter来轻松交换其中任何一个实现:

defmodule MyApp do
  def main do
    greeter().say_hello("Pete") |> IO.puts
  end

  @spec greeter() :: GreeterBehaviour # This doesn't work with dialyzer
  def greeter do
    FormalGreeter # Can easily be swapped to CasualGreeter
  end
end

Dialyzer会成功检查CasualGreeterFormalGreeter是否正确实施GreeterBehaviour行为。但是,如何定义typespec以便Dialyzer检查greeter/0是否返回实际上实现GreeterBehaviour的模块?

使用@spec greeter() :: GreeterBehaviour并不起作用,因为Dialyzer会发出警告:

lib/my_app.ex:19: Invalid type specification for function 'Elixir.MyApp':greeter/0. The success typing is () -> 'Elixir.FormalGreeter'

1 个答案:

答案 0 :(得分:0)

根据您的行为,您可以为say_hello定义类型:

@type f :: (String.t() -> String.t())

您的greeter函数可以返回模块+函数:&module.say_hello/1

规格为:

@spec greeter() :: GreeterBehaviour.f()
defmodule GreeterBehaviour do
  @type f :: (String.t() -> String.t())
  @callback say_hello(String.t()) :: String.t()
end

defmodule FormalGreeter do
  @behaviour GreeterBehaviour

  def say_hello(name) do
    "Good day to you #{name}"
  end
end

defmodule CasualGreeter do
  @behaviour GreeterBehaviour

  def say_hello(name) do
    "Hey #{name}"
  end
end

defmodule MyApp do
  def main do
    greeter().("Pete") |> IO.puts()
  end

  # This will work with dialyzer
  @spec greeter() :: GreeterBehaviour.f()
  def greeter do
    # Can easily be swapped to CasualGreeter
    &FormalGreeter.say_hello/1
  end
end