Search code examples
functional-programmingprotocolselixirmultimethod

Why Elixir doesn't use Multimethods / Protocols?


Let's see the example from docs:

square = fn(x) -> x * x end
list = [1, 2, 3, 4]
Enum.map(list, square)

Why does it requires to explicitly write Enum.map? Why it doesn't use clean and short notation map [1, 2, 3, 4], square?

The Elixir has Multiple Dispatch & Protocols but it seems for me that it uses it a bit strangely.

If you consider the Polymorphism in OOP or Multimethods / Multiple Dispatch, Protocols in FP the point is to make the code short and terse and free Programmer memory from remembering where the method come from.

So, in OOP it would look like code below:

 list.map(square) 

In FP multimethod it would looks like

map(list, square)

In both cases the compiler / interpreter uses the type of arguments to figure out what map method it should be using.

Why Elixir doesn't uses same approach? Why it requires to write verbose code and put responsibility of deciding where the function comes from on the shoulders of programmer?

Sometimes it makes sense to not use multi method and explicitly specify it, like HttpServer.start(80). But for general methods like each, get, set, size etc. it seems like it's much easier to use it without the explicitly specifying where it comes from.

P.S.

It seems that it's actually possible to do so with Protocols in Elixir. I wonder - why then it's not used? All the code in Elixir projects I saw on GitHub use long verbose notations like ModuleName.fnName. Not sure why it is so. Does the usage of Protocols discouraged, or too complicated to be used in everyday tasks?


Solution

  • You can use Enum.map with different arguments in an extensible way because it is implemented with protocols:

    iex> Enum.map [1, 2, 3], fn x -> x * x end
    [1, 4, 9]
    
    iex> Enum.map 1..3, fn x -> x * x end
    [1, 4, 9]
    

    You can also write Enum.map as map as long as you import the Enum module:

    iex> import Enum
    iex> map [1, 2, 3], fn x -> x * x end
    [1, 4, 9]
    

    We simply don't include the Enum module by default. It is much better to explicitly import it so anyone reading your code has a better idea from where the functions being used come from.

    In other words, multiple dispatch and protocols still do not change the fact the code always exist inside modules and calls are always qualified unless imported.