Question

The following code is supposed to read a file and create an Item record for each line:

defmodule Ship do
  defrecord Item, product_code: 0, quantity: 0, destination: ""

  def load_data do
    File.read!("data") 
      |> String.split 
      |> Enum.map &(String.split &1, ",")
      |> Enum.map &(list_to_item &1)
  end

defp list_to_item([pc, q, d | []]) do
    {parsed_q, _} = Integer.parse q
    Item.new product_code: pc, quantity: parsed_q, destination: d
  end
end

There is a data file with the following content:

1,100,London
1,30,Lisbon
3,2,Braga

The problem is, when I execute the load_data function, it looks like the last call to Enum.map is trying to call the parent function (load_data) recursively, and throwing a BadArityError.

The output is as follows:

iex(1)> Ship.load_data
** (BadArityError) #Function<0.127338698/1 in Ship.load_data/0> with arity 1 called with 2 arguments ({:cont, []}, #Function<30.103209896/2 in Enum.map/2>)
    (elixir) lib/enum.ex:2000: Enumerable.Function.reduce/3
    (elixir) lib/enum.ex:879: Enum.map/2
            ship.ex:8: Ship.load_data/0

The interesting part is that if I change the load_data function to:

def load_data do
  list = File.read!("data") 
    |> String.split 
    |> Enum.map &(String.split &1, ",")

  Enum.map list, &(list_to_item &1)
end

It works as expected:

iex(2)> Ship.load_data
[Ship.Item[product_code: "1", quantity: 100, destination: "London"],
 Ship.Item[product_code: "1", quantity: 30, destination: "Lisbon"],
 Ship.Item[product_code: "3", quantity: 2, destination: "Braga"]]

So, any idea on where is the error is coming from in the first version? Aren't they supposed to be equivalent?

Btw, I'm using Elixir 0.12.4.

Was it helpful?

Solution

Your code is actually equivalent to this:

File.read!("data") 
|> String.split 
|> Enum.map (&(String.split &1, ",") |> Enum.map &(list_to_item &1))

You need more parentheses, because |> has a higher precedence than parenthesisless function application:

File.read!("data") 
|> String.split 
|> Enum.map(&(String.split &1, ","))
|> Enum.map(&(list_to_item &1))

This is an unfortunate flaw in the design of Elixir (and the sole reason I still prefer Erlang).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top