šŸ¤ Zips in Elixir ā€” Using protocols for triple dispatch and ultimate flexibility.

Adz
3 min readJul 27, 2019

--

Photo by Tomas Sobek on Unsplash

Protocols are a very powerful feature of Elixir. Iā€™ve introduced them before here, and I looked at how you might use them to solve the expression problem here.

Today I want extend an idea I touched on in that last article and explain it better. It doesnā€™t matter if you havenā€™t read the previous article, but for context at the end of it I tried to show an example of triple dispatch. The idea was that you could decide which specific function gets executed according to the types of three arguments passed to a function. Usually we use single dispatch ā€” we pass an argument into a function and because that argument is a string (say), it does one thing, if it were a different type, then it would do another. This is the essence of polymorphism ā€” a single interface, but varying implementations depending on the type of one thing. But what would it look like if we needed the specific implementation of a function we wanted to run to be determined by the types of three things? Is there ever even a case where that may be useful? Well letā€™s seeā€¦

Our challenge is to implement a library to help us zip together all kinds of data types. To look at what zipping a data type would do, letā€™s take a list as an example. Letā€™s say we want to zip them together but apply a transformation as we do that. The transformation will be to add. Zipping would be taking the first element of list_1, then adding it to the first element of list_2, then taking the second element of list_1and adding it to the second element of list_2. We keep adding the elements from each list pairwise until we run out of elements in the first list. We are returned a new list containing the results of those transformations. We could implement it like this:

Now letā€™s imagine we want to implement this in such a way that it is extensible. That should be simple enough if we implement the Zip as a protocol, then users of our library could implement Zip for their own data types:

This is certainly okay, but there is more we can do. We have allowed extensibility in one direction ā€” for the type of collection that we can zip together. But there are three dimensions to this problem:

  1. The collections we are zipping together (think list, stream, mapā€¦)
  2. The operation we want to perform when we zip (add, subtract, joinā€¦)
  3. The datatype of the elements in the collections (string, integer, decimalā€¦)

Using protocols we can create a library that is extendable in every one of those directions, without violating the open closed principle. It also comes with a huge benefit as we will see, even if the style is a little strange.

We already have the type of collection we can zip together extensible, now letā€™s have a go at making the operation we want to perform extensible. We will do that by also making it a protocol.

Okay, so now letā€™s bring this all together. We slightly change the way we call Zip.apply now, passing in the protocol name as the last argument, and calling calculate on that:

This is very cool. In one fell swoop all three dimensions are extensible. To see how, letā€™s add an implementation of the Add protocol for the decimal data type. This will be good for if we want to add two lists of decimals:

Okay, what about if we want a new operation? Well we just define it as a protocol with a calculate function. Letā€™s do subtract:

Amazing. But now is the really cool thing. Letā€™s implement the Zip protocol for a map:

Once thatā€™s done, we get all of those operations we defined for the list available for map, for free!

Iā€™ve been experimenting with this approach over on a branch of my zip library github. I wonder if Iā€™ll ever use this in a real applicationā€¦?

--

--

No responses yet