One advantage I see with elixir’s protocols is consistent naming of functions with similar implementations. As in, there’s no guarantee that a datastructure will use the name map
for maps or andThen
for monadic binds, as is idiomatic within the elm community. Most people tend to favor consistent semantics. It makes swapping between choices of datastructure easier, not to have to remember the domain-specific name for that pattern
But it’s somewhat tangent. The only way polymorphism would apply to these typeclasses is the Java way of doing implicit castes. Say you have a datastructure foo: Foo
, with a protocol implementation for toString
. Then you could write
foo ++ " is a magnificient datastructure"
instead of
(toString foo) ++ "is a magnificent datastructure"
So the advantage would be that you don’t have to remember what name the Foo
module author used for the toString
implementation.
But of course, reading the code, you may have no clue what method of Foo
specifically turns it into a String
. For example, say Foo
has the methods toWords
and toText
. Which one does foo ++ "a string"
call? First of all, the module author is sorta dumb for doing this because the users have to just remember what the module author thinks the difference is between "words and “text”. This can get pretty nuanced, sort of like Elixirs distinction between “size” and “length” (I might have preferred “capacity” and “count”, but I digress). Second, the readers and writers have to look up the protocol implementation and remember, on top of these nuances, which the author picked as default. And this can vary from module to module. That’s a lot of cognitive burden!
This is why I, personally, feel differently about polymorphism; I like the explicit rules around type casting, I think explicit castes are the best way to predict how code will behave. It lowers the cognitive burden in cases where a datastructure does different things in different contexts by parameterizing the context, instead of leaving it implicit. Whether or not a language has protocols, it still relies on idiomatic naming to convey appropriate use. Without relying on implicit castes, the author’s previously complex distinction between toWords
and toText
, although arbirtary, becomes an advantage. The user of the Foo
module has the freedom to choose either implementation to best suite the use-case, and is reminded by the compiler if they forget to make the choice.
But CMV! I’m sure you have a lot more first-hand experience with Elixir’s type system since I have none.