Reputation: 3797
I want to perform some analytics on a series of combinations of values.
I have the following function, but for some reason, after the comprehensions are completed and at the end of the function body, the variable analytics
is still an empty list, while it's not inside the comprehension on each iteration
Any ideas?
def handle_cast({:start}, state) do
intervals = [7, 30, 90]
groupings = ["day_of_week", "time_of_day"]
aggregators = [
%{
domain: "support",
metric: "new_conversations",
func: &App.get_new_conversations/2
},
%{
domain: "support",
metric: "closed_conversations",
func: &App.get_closed_conversations/2
},
%{
domain: "support",
metric: "median_response_time",
func: &App.get_median_response_time/2
},
]
Repo.transaction(fn ->
Repo.delete_all(Analytic)
analytics = []
for interval <- intervals do
for grouping <- groupings do
for %{domain: domain, metric: metric, func: func} <- aggregators do
analytic =
func.(grouping, interval)
|> Enum.map(fn %{"app_id" => app_id, "data" => data} = result ->
%Analytic{app_id: app_id, domain: domain, metric: metric, grouping: grouping, interval_in_days: interval, data: data}
end)
analytics = [analytic|analytics]
end
end
end
end)
{:noreply, state}
end
Upvotes: 2
Views: 77
Reputation: 222118
Variables in Elixir are immutable but rebindable. What this means is that the line analytics = [analytic|analytics]
is creating a new list and binding it to the variable named analytics
for the scope of that block. When the block ends, the changes are not persisted in the next iteration of the for
. For example:
iex(1)> x = 1
1
iex(2)> for i <- 1..3 do
...(2)> IO.puts(x); x = x + i; IO.puts(x)
...(2)> end
1
2
1
3
1
4
For the code you've written, you can use the fact that for
returns a list of the values of the last expression inside them and store the return value of the outermost for
to analytics
, but there's a slight problem: you'll end up with nested lists:
iex(1)> for i <- 1..2 do
...(1)> for j <- 1..2 do
...(1)> for k <- 1..2 do
...(1)> {i, j, k}
...(1)> end
...(1)> end
...(1)> end
[[[{1, 1, 1}, {1, 1, 2}], [{1, 2, 1}, {1, 2, 2}]],
[[{2, 1, 1}, {2, 1, 2}], [{2, 2, 1}, {2, 2, 2}]]]
But, there's a simple solution! for
accepts multiple <-
clauses in a single call and automatically returns a flat list:
iex(1)> for i <- 1..2, j <- 1..2, k <- 1..2 do
...(1)> {i, j, k}
...(1)> end
[{1, 1, 1}, {1, 1, 2}, {1, 2, 1}, {1, 2, 2}, {2, 1, 1}, {2, 1, 2}, {2, 2, 1},
{2, 2, 2}]
Using this method, your code becomes:
analytics =
for interval <- intervals,
grouping <- groupings,
%{domain: domain, metric: metric, func: func} <- aggregators do
func.(grouping, interval)
|> Enum.map(fn %{"app_id" => app_id, "data" => data} = result ->
%Analytic{app_id: app_id, domain: domain, metric: metric, grouping: grouping, interval_in_days: interval, data: data}
end)
end
end
end
This should give you the same output you were most likely expecting from the original code.
Upvotes: 3