tomr
January 2, 2021, 12:00am
1
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!
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
tomr
January 2, 2021, 11:01am
6
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