Question Piping

Hello - I am quite new to elixir (and well programming itself…) and I have a question regarding piping:

  def total_budget(budget_items) do
      Enum.reduce(budget_items, fn item, acc ->
        %{amount_in_cents: item.amount_in_cents + acc.amount_in_cents}
      end)[:amount_in_cents]
    |> value_in_euro()
  end

I am unable to pipe the Enum.reduce - this gives me:

  def total_budget(budget_items) do
    budget_items
    |> Enum.reduce(fn item, acc ->
        %{amount_in_cents: item.amount_in_cents + acc.amount_in_cents}
      end)[:amount_in_cents]
    |> value_in_euro()
  end

function Enum.reduce/1 is undefined or private

I would also be happy if one could tell me how to pipe that [:amount_in_cents], so the code is less spaghetti-i.

Thanks a lot! :slight_smile:

1 Like

budget_items appears to be a list of maps with a key of amount_in_cents

Your reduce function should probably look like this

Enum.reduce(budget_items, 0, fn(item, acc) ->
  item.amount_in_cents + acc
end

You’ll see functions like this often shortened using the capture operator

Enum.reduce(budget_items, 0, & &1.amount_in_cents + &2)

Then you can pipe the output to value_in_euro()

Enum.reduce(budget_items, 0, & &1.amount_in_cents + &2)
|> value_in_euro()

In my opinion you shouldn’t go crazy with piping unless it makes sense and simplifies your code.

You cannot pipe it like that, as what is Elixir seeing is:

foo |> (Enum.reduce(…)[:amount_in_cents]

Instead of what you would expect:

(foo |> Enum.reduce(…))[:amount_in_cents]

Fortunately we can fix that:

budget_items
|> Enum.reduce(fn item, acc ->
    %{amount_in_cents: item.amount_in_cents + acc.amount_in_cents}
end)
|> Map.fetch!(:amount_in_cents)
|> value_in_euro()

However that is still suboptimal, as we do not need to have map inside of the reduce:

budget_items
|> Enum.reduce(0, fn item, acc ->
    acc + item.amount_in_cents
end)
|> value_in_euro()

Or if we want to go really fancy:

budget_items
|> Enum.reduce(0, & &2 + &1.amount_in_cents)
|> value_in_euro()
5 Likes

With your pipe containing reduce- you used the [:amount_in_cents] in the piping “function”, Elixir tried calling (reduce(...)[:amount_in_cents]).(budget_items), so the reduce was not part of the pipe, but pipe of an expression that was supposed to return a closure that would become the part of the pipe. An approach could be:

  def total_budget(budget_items) do
    budget_items
    |> Enum.reduce(fn item, acc ->
        %{amount_in_cents: item.amount_in_cents + acc.amount_in_cents}
      end)
    |> Map.get(:amount_in_cents)
    |> value_in_euro()
  end
1 Like

This will work but like @hauleth mentions in his answer. It’s unnecessary to create another map inside the reduce.

1 Like

Thanks everyone that helped me a ton! Special thanks to @hauleth for the detailed explanation - I went for the “not so fancy” version as I am think this is more readable for me later :slight_smile: