Reputation: 2246
In Elixir how can I document that a function will return a module that implements a specific behaviour?
To use a trivial example, say I have created a GreeterBehaviour
behaviour that is implemented by two modules:
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
I then want to easily swap out either of those implementations by retrieving the Greeter via a function:
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 will successfully check that both CasualGreeter
and FormalGreeter
correctly implement the GreeterBehaviour
behaviour. However, how can I define a typespec so that Dialyzer will check that greeter/0
returns a module that does in fact implement GreeterBehaviour
?
Using @spec greeter() :: GreeterBehaviour
doesn't work as Dialyzer will throw a warning:
lib/my_app.ex:19: Invalid type specification for function 'Elixir.MyApp':greeter/0. The success typing is () -> 'Elixir.FormalGreeter'
Upvotes: 16
Views: 1382
Reputation: 64
In your behaviour you could define a type for say_hello
:
@type f :: (String.t() -> String.t())
Your greeter
function could return a module + function: &module.say_hello/1
And the spec would be:
@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
Upvotes: 0