Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Lesson: Protocols #825

Merged
merged 1 commit into from
Feb 9, 2017
Merged

New Lesson: Protocols #825

merged 1 commit into from
Feb 9, 2017

Conversation

ybur-yug
Copy link
Contributor

@ybur-yug ybur-yug commented Nov 27, 2016

Add Protocols Lesson

This is a first run at a lesson on Protocols for the advanced section from what I started with and finished with @doomspork's feedback.

TODO

@ybur-yug ybur-yug added enhancement An enhancement to Elixir School content new lesson An completely new lesson (not a translation) and removed enhancement An enhancement to Elixir School content labels Nov 27, 2016
Copy link
Member

@doomspork doomspork left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some suggestion changes, thoughts?

lang: en
---

In this lesson we are going to look at Protocols, and how to leverage them in Elixir.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this lesson we are going to look at Protocols, what they are, and how we use them in Elixir.

{% include toc.html %}

## What Are Protocols
Protocols are the chosen method for polymorphism in Elixir.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what are they? Protocols are a means of achieving polymorphism in Elixir. One pain of Erlang is extending an existing API for newly defined types. To avoid this in Elixir the function is dispatched dynamically based on the value's type. Elixir comes with a number of protocols built in, for example the String.Chars protocol is responsible for the to_string/1 function we've seen used previously. Let's take a closer look at to_string/1 with a quick example:

"foo"
```

As you can see, we have called this on multiple types.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you can see we've called the function on multiple types and demonstrated that it work on them all. What if we call to_string/1 on tuples (or any type that hasn't implemented String.Char)? Let's see:

```

As you can see, we get a protocol error.
This is because there is no implementation for the Protocol in the tuples.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you can see we get a protocol error as there is no implementation for tuples. In the next section we'll implement the String.Chars protocol for tuples.

Implementing a protocol

We saw that to_string/1 has not yet been implemented for tuples so let's add it. To create an implementation we'll use defimpl with our protocol, and provide the :for option and our type. Let's take a look at how it might look:

defimpl String.Chars, for: Tuple do
  def to_string(tuple) do
    insides = 
      tuple
      |> Tuple.to_list
      |> Enum.map(&Kernel.to_string/1)
      |> Enum.join(", ")

    "{#{insides}}"
  end
end

If we copy this into IEx we should be now be able to call to_string/1 on a tuple without getting an error:

iex> to_string({3.14, "apple", :pie})
"{3.14, apple, pie}"

This is because there is no implementation for the Protocol in the tuples.
To implement a protocol, `defimpl/3` is our tool of choice.

## Working With Protocols
Copy link
Member

@doomspork doomspork Nov 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we use an example that can be applied to a few different types? Something like:

We know how to implement a protocol but how we do we define a new one? For our example we'll implement to_atom/1, let's see how to do that with defprotocol:

defprotocol AsAtom do
  def to_atom(data)
end

defimpl AsAtom, for: Atom do
  def to_atom(atom), do: atom
end

defimpl AsAtom, for: BitString do
  defdelegate to_atom(string), to: String
end

defimpl AsAtom, for: List do
  defdelegate to_atom(list), to: List
end

Here we've defined our protocol and it's expected method, to_atom/1, along with implementations for a few types. Now that we have our protocol, let's put it to use in IEx:

iex> import AsAtom
AsAtom
iex> to_atom("string")
:string
iex> to_atom(:an_atom)
:an_atom
iex> to_atom([1, 2])
:"\x01\x02"

@doomspork
Copy link
Member

@ybur-yug thoughts on the feedback?

@ybur-yug
Copy link
Contributor Author

@doomspork let me read over this all after work today and give another pass :)

@ybur-yug
Copy link
Contributor Author

Finished a cleanup of a first proposed draft with the whole lesson after initial start + feedback. Feel to comment/review etc, all.

Copy link
Member

@doomspork doomspork left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ybur-yug looks good, one comment.

:"\x01\x02"
```

As we can see, protocols are a powerful way to achieve polymorphism, but we also want to be cautious with what features we spread between modules to avoid unintended results.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part I'm not sure I get: ", but we also want to be cautious with what features we spread between modules to avoid unintended results."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you implement stuff that just catches all and returns something (possible, but stupid) you are going to have a bad time and not get errors that should be being raised etc. I guess maybe its not worth mentioning. Makes sense without it still, too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ybur-yug that isn't what that sentence conveys though, not to me at least.

Copy link
Member

@nscyclone nscyclone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @ybur-yug!
I've proposed some improvements, looking forward to get your opinion on these.

```

As you can see we've called the function on multiple types and demonstrated that it work on them all.
What if we call `to_string/1` on tuples (or any type that hasn't implemented `String.Char`)?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String.CharString.Chars

"{3.14, apple, pie}"
```

We know how to implement a protocol but how we do we define a new one?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do wedo we

defimpl AsAtom, for: List do
defdelegate to_atom(list), to: List
end
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am not mistaken Map's protocol implementation will not work for structs.
Thus probably it's worth to extend this example by adding a protocol implementation for Map and shortly explain how protocols work with structs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to look into this one. I'm going to give the lesson a second pass today at the airport/on my plane.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea @nscyclone 👍

```

We know how to implement a protocol but how do we define a new one?
For our example we'll implement `to_atom/1`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're missing punctuation here.

Copy link
Member

@doomspork doomspork left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good @ybur-yug! I think we're missing punctuation for one sentence but this is otherwise good-to-go 👍

@ybur-yug
Copy link
Contributor Author

ybur-yug commented Feb 9, 2017

@doomspork incorporated + pushed

@ybur-yug ybur-yug mentioned this pull request Feb 9, 2017
21 tasks
@doomspork doomspork merged commit e588d2d into master Feb 9, 2017
@doomspork doomspork deleted the new_lesson/protocols branch February 9, 2017 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new lesson An completely new lesson (not a translation)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants