user4104697
user4104697

Reputation:

Difference between Enum.each and list comprehension.

In Elixir, we can print the value of a list one by one by using Enum.each function.

a = [1, 2, 3]
Enum.each(a, fn(x) -> IO.puts x end)

the output is:

1
2
3
:ok

Using list comprehension to do the same task:

a = [1, 2, 3]
for x <- a, do: IO.puts x

the outputs is:

1
2
3
[:ok, :ok, :ok]

At the end of the output, Enum.each function returns one :ok, list comprehension returns three :ok. What is the difference between the two approach? Sorry for this dumb question. I read the official elixir doc. but didn't find the answer.

Upvotes: 11

Views: 7143

Answers (3)

Kevin Johnson
Kevin Johnson

Reputation: 1970

The answers provided so far, although correct do not really address the core of your problem. The core of your problem here is that you are unfamiliar with how Enum.each, Enum.map, for x <- a etc. are implemented under the hood.

Enum.map under the hood is nothing but a recursive function that accepts two inputs:

  1. a list
  2. a function that needs to be applied to each element in that list

The return value of this recursive function is a new list.

To clarify even more: say you have as input the list [1, 2, 3, 4] and a function: fn e -> e * 2 end then you will get the new list [2, 4, 6, 8]

Enum.map could simply be implemented like this:

defmodule MyEnum do
  def map(list, fun) do 
    if Enum.empty?(list) do
      []                                # Null case reached, stop recursing.
    else
      [head | tail] = list              # obtain the first element in the list, assign that value to `head`. Store the remaining list inside the variable `tail`
      projected_value = fun.(head)        # This is where your function gets applied
      [ projected_value | map(tail, fun)] # Recurse
    end
  end
end

So MyEnum.map([1,2,3], fn e -> e * 2 end) will return to you the list: [2, 4, 6]

Now suppose that you utilize the function IO.puts/2 here, like: MyEnum.map([1,2,3], fn e -> IO.puts(e) end), the variable projected_value will eventually be the return value of IO.puts/2. It prints the value to the screen, and it returns an :ok atom.

This means that the list you are building up in this case would be accumulating the atoms :ok over each new recursion.

The comprehension for x <- a under the hood is something similar to Enum.map, a recursive function that will apply a provided function to each element of your list, but just with a different syntax.

Enum.each is also a recursive function under the hood, but it will just loop over every element, not caring about accumulating anything in the process. This is how you could implement it:

defmodule MyEnum do
  def each(list, fun) do 
    if Enum.empty?(list) do
      :ok                          # Return `:ok` to communicate the entire list has been traversed
    else
      [head | tail] = list
      fun.(head)                   # Only apply the function to the element of the list, and do not care about its return value
      each(tail, fun)              # Continue to traverse the list recursively
    end
  end
end

As you can see, this function will always return :ok and Enum.map will always return a new list.

All of this is elementary stuff, so:

  1. If your understand what is happening under the hood and;
  2. you understand that in essence Enum.map and for x <- [1,2,3] are basically one and the same recursive function with just a different interface

|> then all your questions should be clarified.

In addition, the same constructs are provided in Javascript as well. If you do [1,2,3,4].map or [1,2,3,4].each in JS, they do the exact same thing as in Elixir, so you could find more clarity in understanding these constructs in Javascript first if you still have a problem understanding the differences.

Upvotes: 12

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121020

Simply put, Enum.each/2 is an iteration, while Enum.map/2 and the comprehension are both mappers, and the comprehension could be a reducer at some extent.

Upvotes: 1

PatNowak
PatNowak

Reputation: 5812

Enum.each/2 would be good fit, if you want to do something over the collection and you don't care about the result. :ok is always return as a result of this function.

List comprehension returns you new collection, which is the result of computation of function call for each element of the original enumable thing (in your case list). Think about it as alternative for Enum.map/2.

In your case result is [:ok, :ok, :ok], because :ok is also the result for each IO.puts call.

Upvotes: 3

Related Questions