For ... into: %{}, do:

According to the snippet below.

iex(24)> for c <- cols, r <- rows, into: %{}, do: {c, r}
%{1 => 10, 2 => 10, 3 => 10, 4 => 10, 5 => 10, 6 => 10, 7 => 10, 8 => 10,
  9 => 10, 10 => 10}

The into: option should call this &Enum.into/2

def into(enumerable, %{} = collectable) when is_list(enumerable) do
    Map.merge(collectable, :maps.from_list(enumerable))
  end

which called the Erlang maps module function from_list(List)

from_list(List) -> Map
Takes a list of key-value tuples elements and builds a map. 
The associations can be in any order, and both keys and values in the association can be of any term. 
If the same key appears more than once, the latter (right-most) value is used and the previous values are ignored.

Is that right?

How’s :maps.from_list(List) getting rid of the duplicate keys?
It first transforms the list into a “no_duplicate_keys” list and then apply a recursive function to create the map, or the get_rid_of_duplicate_keys transformation is part of the recursive function and the raw list is passed to it?

BTW, Elixir doc is awesome!

Does it though? It would be helpful if you posted a link to the code.

I thought into: %{} just makes elixir’s code get transformed to erlang’s map comprehension

#{ E0 => E1 || K := V <- M0 }
% or
#{ I => f(I) || I <- list() }

instead of a list comprehension (if no :into is provided)

[I || <– [1, 2, 3]]

Here’s the code that’s get executed to find into: https://github.com/elixir-lang/elixir/blob/master/lib/elixir/src/elixir_erl_for.erl#L16-L20

:maps.from_list is a BIF (built-in function) - it is implemented in C in the bowels of the virtual machine.

Erlang doesn’t have map comprehensions. They were planned but never implemented.

Elixir can’t use Erlang’s comprehensions, since Elixir’s for works with any enumerable and not just lists.

2 Likes

Is there any overhead of elixir’s comprehensions compared with erlang’s then?

Like

  • Compile for that don’t do any filtering into Enum.map instead of Enum.reduce.
  • Compile for that discards result into Enum.each instead of Enum.reduce with nil as an accumulator.

from

erlang doesn’t seem to have such problems http://erlang.org/doc/efficiency_guide/listHandling.html#id66810, does it?

I assume it :wink: The behaviours of for … into: %{} and Enum.into() look very similar.

After looking for the source code of “for” in the elixir doc, I found only this dark defmacro special_forms :dog:

Elixir comprehensions compile completely differently from Erlang comprehensions. Because of the polymorphism (and implementation using Enum.reduce/3 and closures), they are less efficient. This is different for binary comprehensions for <<x <- string>>, do: ..., which are the same as Erlang binary comprehensions.

Elixir comprehensions don’t use Enum.into/1. They compile into a direct use of the Collectable protocol with a special optimisation for into: %{} (an empty map), which compiles to calls into :maps.put.

Here are some decompilations. Given a following Elixir module:

defmodule Test do
  def for_enum(list) do
    for x <- list, do: x * 2
  end

  def for_bin(bin) do
    for <<x <- bin>>, do: x * 2
  end

  def for_bin_into(bin) do
    for <<x <- bin>>, into: "", do: x * 2
  end

  def for_enum_into(list) do
    for x <- list, into: %{}, do: {x, x * 2}
  end

  def for_enum_into_noinline(list, coll) do
    for x <- list, into: coll, do: x * 2
  end
end
for_bin(bin@1) ->
    [ x@1 * 2 || <<x@1/integer>> <= bin@1 ].

for_bin_into(bin@1) ->
    << <<(x@1 * 2)/bitstring>> || <<x@1/integer>> <= bin@1 >>.

for_enum(list@1) ->
    lists:reverse('Elixir.Enum':reduce(list@1,
                                       [],
                                       fun(x@1, _@1) ->
                                              [x@1 * 2|_@1]
                                       end)).

for_enum_into(list@1) ->
    'Elixir.Enum':reduce(list@1,
                         #{},
                         fun(x@1, _@1) ->
                                begin
                                    {_@3,_@4} = {x@1,x@1 * 2},
                                    maps:put(_@3, _@4, _@1)
                                end
                         end).

for_enum_into_noinline(list@1, coll@1) ->
    {_@1,_@2} = 'Elixir.Collectable':into(coll@1),
    try
        'Elixir.Enum':reduce(list@1,
                             _@1,
                             fun(x@1, _@1) ->
                                    _@2(_@1, {cont,x@1 * 2})
                             end)
    of
        _@6 ->
            _@2(_@6, done)
    catch
        _@3:_@4 ->
            _@5 = erlang:get_stacktrace(),
            _@2(_@1, halt),
            erlang:raise(_@3, _@4, _@5)
    end.
5 Likes

Is there any reason why for the into: %{} case it couldn’t do a comprehension into a list, and then use :maps.from_list?

1 Like

Yeah, it would be possible. Ideally, though, they would have the same cost at runtime.