Skip to content

Latest commit

 

History

History
285 lines (210 loc) · 6.61 KB

protocols.livemd

File metadata and controls

285 lines (210 loc) · 6.61 KB

Protocols

Mix.install([
  {:youtube, github: "brooklinjazz/youtube"},
  {:hidden_cell, github: "brooklinjazz/hidden_cell"},
  {:tested_cell, github: "brooklinjazz/tested_cell"},
  {:utils, path: "#{__DIR__}/../utils"}
])

Navigation

Return Home Report An Issue

Setup

Ensure you type the ea keyboard shortcut to evaluate all Elixir cells before starting. Alternatively, you can evaluate the Elixir cells as you read.

Protocols

In English, a protocol means a set of rules or procedures. In Elixir, a protocol allows us to create a common functionality, with different implementations.

Specifically, protocols enable polymorphic behavior based off of data.

flowchart
  A -- data --> B
  B --> C
  B --> D
  B --> E
  B --> F

  A[Caller]
  B[Protocol]
  C[Implementation]
  D[Implementation]
  E[Implementation]
  F[Implementation]
Loading

For example, the Enum module works with any collection data type. Under the hood, it uses the Enumerable protocol.

flowchart
  E[Enum]
  EN[Enumerable]
  E --> EN
  EN --> Map
  EN --> List
  EN --> k[Keyword List]
Loading

It then executes the appropriate instructions depending on which data type the Enum function is called with.

Enum.random(%{one: 1, two: 2})
Enum.random(1..20)
Enum.random(one: 1, two: 2)

Protocols On Simple Data Types

Anytime you need a common function for multiple data types or structs, you can consider a protocol.

For example, let's create an Adder protocol that's going to add two values together. It will accept integers, strings, and lists and hide the specifics of which operator is necessary to add different types.

Adder.add(1, 2)
3

Adder.add("hello, ", "world")
"hello, world"

Adder.add([1], [2])
[1, 2]

So if we give the protocol an integer, it will use the implementation for Integer. If we provide the protocol a string, it will use the implementation for BitString, and if we provide the protocol a list, it will use the implementation for List.

flowchart
  A -- Integer --> B
  B -- Integer --> C
  B -- BitString --> D
  B -- List --> E
  style C color:green
  style D color:red
  style E color:red
  A[Caller]
  B[Adder Protocol]
  C[Integer Implementation]
  D[String Implementation]
  E[List Implementation]
Loading

List, Integer, and BitString are all built-in Elixir modules used to define which data type the protocol implementation is for.

We define a protocol using defprotocol and define a function clause. We only need the function head, not the function body.

defprotocol Adder do
  def add(value, value)
end

We've defined a protocol above, but we haven't implemented it for any data type yet.

Notice the error for Adder.add/2 called with two integers says protocol Adder not implemented for 1 of type Integer

Adder.add(1, 2)

To define an implementation for a protocol, we use defimpl and provide it the name of the protocol. We also declare what struct or data type the protocol is for:

defimpl Adder, for: Integer do
  def add(int1, int2) do
    int1 + int2
  end
end

Adder.add(1, 2)

We also want the Adder protocol to handle strings and lists. That means we need to create an implementation for List and String. In Elixir, the underlying type for strings is BitString.

Why BitString? In Elixir, strings are stored as bitstrings.

defimpl Adder, for: BitString do
  def add(string1, string2) do
    string1 <> string2
  end
end

Adder.add("hello, ", "world")

Your Turn

In the Elixir cell below, create an implementation of Adder for lists.

Example Solution
defimpl Adder, for: List do
  def add(list1, list2) do
    list1 ++ list2
  end
end

Protocols With Structs

We can create implementations for a protocol based on simple data types such as integer and string. In addition to those simple data types, we can also create an implementation for specific structs.

For example, let's say we're making an old school kids toy that says different animal noises.

we'll create a Sound protocol that prints different sounds depending on the struct given to it.

defprotocol Sound do
  def say(struct)
end

We'll create a Cat struct.

defmodule Cat do
  defstruct [:mood]
end

Then a Cat implementation for the Sound protocol.

defimpl Sound, for: Cat do
  def say(cat) do
    case cat.mood do
      :happy -> "Purr"
      :angry -> "Hiss!"
    end
  end
end
Sound.say(%Cat{mood: :happy})
Sound.say(%Cat{mood: :angry})

Your Turn

defmodule Dog do
  defstruct []
end

Define a Sound implementation for the Dog struct above. When Sound.say/1 is called with a Dog struct it should return "Woof!"

Sound.say(%Dog{})
"Woof!"
Example Solution
defimpl Sound, for: Dog do
  def say(dog) do
    "woof!"
  end
end

Commit Your Progress

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 protocols section"

Up Next

Previous Next
Games Documentation And Static Analysis Math With Protocols