Mix.install([
{:youtube, github: "brooklinjazz/youtube"},
{:hidden_cell, github: "brooklinjazz/hidden_cell"},
{:tested_cell, github: "brooklinjazz/tested_cell"},
{:utils, path: "#{__DIR__}/../utils"}
])
Ensure you type the ea
keyboard shortcut to evaluate all Elixir cells before starting. Alternatively, you can evaluate the Elixir cells as you read.
Behaviours allow us to create modules that all share a common pattern but need different implementations.
You can define a behaviour, and then two other modules will implement it.
flowchart
A[Behaviour] --> B[Module]
A[Behaviour] --> C[Module]
This enforces that both modules will implement a shared application programming interface or API. What is an API? Well, in this specific context the API is the set of public-facing functions in a module. More generically an interface is the point at which two systems interact. Since we're talking about systems for programming applications, we call it the application programming interface.
To define a behaviour, we start by defining a module. However, instead of implementing
functions, we create @callback
module attributes. These @callback module attributes
define the signature of a function.
The signature of a function is the name, params, and expected return value, but not the implementation. Essentially, it's the expected input, and output types in our program, but not the actual implementation.
To define the expected return value we use ::
and then the data type we expect to return.
Elixir provides a set of types. You can see the full list of types in the Typespecs
documentation. For now, we're going to use the String.t()
type. Some other common
types are atom()
, boolean()
, integer()
, float()
, and any()
if you don't require a specific output.
defmodule Villain do
@callback catchphrase() :: String.t()
end
Now a module can use the Villain
behaviour using the @behaviour
module attribute.
You'll notice that Elixir displays a warning because the DarthVader
module is supposed
to implement a catchphrase/0
function.
defmodule DarthVader do
@behaviour Villain
end
To remove the warning, we need to implement my_function/0
and it should return a string.
defmodule DarthVader do
@behaviour Villain
def catchphrase do
"No, I am your father."
end
end
DarthVader.catchphrase()
So, why use a behaviour?
Primarily, behaviours ensure that any module that implements the behaviour provides the expected set of functions and that those functions match the expected function signatures.
Behaviours are another way to achieve polymorphism
. We can define
a behaviour, and then several different modules can implement that behaviour. Behaviours
allow us to create "templates" for modules to ensure each module is consistent.
The caller, which is the place in the code where we call a module's function, can then have a consistent experience between modules.
flowchart
A --> C
A --> D
A --> E
A --> F
C --> B
D --> B
E --> B
F --> B
A[Caller]
B[Behaviour]
C[Implementation]
D[Implementation]
E[Implementation]
F[Implementation]
If the behaviour ever needs to change, we can modify the behaviour, and know which modules need to be updated thanks to the warnings Elixir provides.
You can also implement behaviours with dynamic dispatching which allows the caller to deal with a single module, and swap out the implementation.
flowchart
A --> B
B --> C
B --> D
B --> E
B --> F
A[Caller]
B[Behaviour]
C[Implementation]
D[Implementation]
E[Implementation]
F[Implementation]
But that's beyond the scope of this lesson.
In the Elixir cell below, convert the Pet
module into a behavior.
The Pet
behavior should define an @callback
for a speak/0
function that returns a string.
defmodule Pet do
end
Once complete, re-evaluate the following module. It should display a warning.
speak/0 required by behaviour Pet is not implemented (in module Dog)
Add a speak/0
function to the Dog
module. When you re-evaluate the cell, the warning should be gone.
defmodule Dog do
@behaviour Pet
end
Run the following in your command line from the beta_curriculum folder to track and save your progress in a Git commit.
$ git add .
$ git commit -m "finish behaviours section"