Using Protocols to decouple implementation details
26 Sep 18
Protocols are a way to implement polymorphism in Elixir. We can use it to apply a function to multiple object types or structured data types, which are specific to the object itself. There are two steps; defining a protocol in the form of function(s), and one or many implementations for that protocol.
You’ve probably seen this example before either in Elixir or as an Interface in other languages:
What is Polymorphism
Polymorphism is the provision of a single interface to entities of different types.
I think this definition best explains what Polymorhism is in Elixir Protocols, as you define a single protocol that is used as an interface to different structured data types, keeping implementation separate from your calling code.
The goal of Polymorphism is to define abstractions around how types are used in your application, including which operations or functions are able to be performed on them. These abstractions allow your code to be decoupled from implementation details that aren’t relevant.
In Elixir, this means that we can define implementations of specific protocols, and then call the protocol functions on any of those object types, without knowing which object it is at run-time.
A typical situation you might find yourself in is wanting to translate an internal data structure, to an external one, perhaps for use in an API call.
Let’s say we want to translate a (contrived)
User struct into a
ExternalUser struct, but our calling code should be generic so that it can be used to translate other types as well.
We’ve now decoupled our API pushing service,
MyApp.API is not aware of what it is pushing, only that it needs to transform the structured data first before making the request.
Enumerable - you already use a protocol
In case you weren’t already aware, if you’ve been using any
Enum functions, such as
Enum.filter/2, the data types you pass as the first argument to those functions implement the Enumerable Protocol. This particular protocol defines four functions that need to be implemented for any type you wish to use with it
slice/1. You can see these functions defined in the Elixir code on Github.
One of the greatest things about Elixir is you can easily browse source code to see how the standard library we use all the time is implemented internally. In theory, you can implement your own Enumerable type, but I’m not sure how useful that would be in practice!