Background
I am trying to write some code to exemplify the power of behaviours in Elixir.
As a result, I am trying to implement this diagram:
Basically, I have a Car module, which depends on a behaviour (interface) Car.Brake. There are two possible implementations of this behaviour, the Car.Brake.ABS and Car.Brake.BBS respectively.
Code
The way I modeled this concept into code is as follows:
lib/brake.ex:
defmodule Car.Brake do @callback brake :: :ok end lib/brake/abs.ex
defmodule Car.Brake.Abs do @behaviour Car.Brake @impl Car.Brake def brake, do: IO.puts("Antilock Breaking System activated") end lib/brake/bbs.ex
defmodule Car.Brake.Bbs do @behaviour Car.Brake @impl Car.Brake def brake, do: IO.puts("Brake-by-wire activated") end lib/car.exs
defmodule Car do @type brand :: String.t() @type brake_system :: module() @type t :: %__MODULE__{ brand: brand(), brake_system: brake_system() } @enforce_keys [:brand, :brake_system] defstruct [:brand, :brake_system] @spec new(brand(), brake_system()) :: t() def new(brand, brake) do %__MODULE__{ brand: brand, brake_system: brake } end @spec brake(t()) :: :ok def brake(car), do: car.brake_system.brake() @spec change_brakes(t(), brake_system()) :: t() def change_brakes(car, brake), do: Map.put(car, :brake_system, brake) end Objective
The idea here is that I can create a Car and decide (and even change) which breaking system is used at runtime:
> c = Car.new("Honda", Car.Brake.Bbs) %Car{ brand: "Honda", breaking_system: Car.Brake.Bbs } c = Car.change_brakes(c, Car.Brake.Abs) %Car{ brand: "Honda", breaking_system: Car.Brake.Abs } Problem
So far so good. But now comes the problem. I am using dialyzer to check the types. And while the above code sample will not trigger an error in the compiler (nor in dialyzer) the following code sample will also not trigger any errors, while still being obviously wrong:
c = Car.new("honda", Car.WingsWithNoBreaks) The issue here is that Car.WingsWithNoBrakes does not implement the Car.Brakes behaviour, but no one complains because of my lax typespec @type brake_system :: module()
Questions
With this in mind, I have several questions:
- How can I make my typespec more specific, to verify the module being passed actually implements the behaviour?
- If this is not possible, is there a more idiomatic way of specifying this dependency clearly?
- What is the idiomatic way in elixir to specific that âmodule Aâ depends on an implementation of âbehaviour Bâ ?