3

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?

Alex Craft
  • 13,598
  • 11
  • 69
  • 133
  • I believe nobody wants to write protocol implementations just for the sake of existence. If you know exactly that your data is list - you write list. – Lol4t0 Nov 07 '15 at 17:14
  • 1
    Please note, *this is not OOP*. The way namespaces work, what is legal to pass around, what data primitives exist, the stark distinction between messages and function/method calls, and [even the basic unit of computation](http://stackoverflow.com/questions/32294367/erlang-process-vs-java-thread) are all radically different. – zxq9 Nov 08 '15 at 11:48

4 Answers4

12

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.

José Valim
  • 50,409
  • 12
  • 130
  • 115
  • Thanks for reply, but protocols is not multi-methods / polymorphism. You can't import `map` from different modules simultaneously, let's say `Enum.map` and `Map.map` - so it's a pretty clear that there's no polymorphism. – Alexey Petrushin Jun 05 '17 at 01:14
  • 2
    Protocols in Elixir provide open ad-hoc polymorphism. The response above shows how it is polymorphic on the first argument. Even regular function clauses in Elixir provide a form of polymorphism called closed ad-hoc polymorphism. Multi-methods is another form of polymorphism, which is not supported by Elixir. Generics are also another form of polymorphism, called parametric polymorphism, and even inheritance is called subtyping polymorphism. Polymorphism is a very broad term so be careful to not get it mixed up. – José Valim Jun 23 '17 at 21:10
2

In Elixir (and Erlang) functions always live in a module. You can't have a function without a module. Even the function you're calling "bare" are in a module - it's called Kernel (in Erlang they live in :erlang module).

Functions in Erlang/Elixir are identified by module, name and arity - only a combination of those three elements can tell you the real identity of a function, but all of those three are concerned with only naming of the function - not data it acts on.

Protocols, in contrary, are not about naming functions - they are about handling polymorphic data. All Enum functions are backed by the Enumerable protocol - they can work with lists, ranges, maps, sets, etc. So Enum.map is a polymorphic function, but it's called Enum.map and not just map. If you're so bothered by the module names, you can always import the modules you want to use "bare".

michalmuskala
  • 11,028
  • 2
  • 36
  • 47
0

So, in OOP it would look like code below:

list.map(square)

In FP multimethod it would looks like

map(list, square)

In OOP, you take an object (your list) and call a method that this object is implementing, map in this case

In Elixir, you call the map function, stored in the Enum module, with your collection and the function you want to apply. Flat namespacing is not so great when it comes to documentation and such. By referencing all enumerable-related functions under Enum, your documentation makes more sense (but it's not the only reason).

Hécate
  • 1,005
  • 9
  • 14
0

I'm very new to Elixir.

It does seem limiting to me that structs and protocols are only addressing polymorphism on the first parameter. I find multi-methods / multiple dispatch like in Julia, CLOS, and Clojure to be quite a bit more powerful.

However, it looks like you could get pretty close to multi-methods without much effort using multi-parameter guards with the defguard macro.

A comically simple example would look like...

defmodule MyModule do
  defguard both_integers(i, j) when is_integer(i) and is_integer(j)

  def add(i, j) when both_integers(i, j) do
    IO.puts i + j
  end

  def add(i, j) do
    IO.puts "do something completely different"
  end
end
Ben Racine
  • 541
  • 4
  • 15