Changing variables in a loop

Hi all, the question seems like a simple question, but it has thrown me for a long time. I have a loop in which I loop through an element of the map list and I need to add the current item to the new list.
For a better understanding, here is a simpler example:

current_list = []
for x <- 1..10 do
current_list = [x | current_list]
end

I expect to get [1,2,3,4,5,6,7,8,9,10] in current_list, but got []
What am I doing wrong? Thanks in advance!

The current_list inside the for-loop is not the same variable as the current_list outside the for-loop. Actually, the current_list inside each loop is different.

I bet you’ve heard that in Elixir, everything is immutable, which means if a list is empty, it stays empty for the rest of its life. You can’t stuff things into it, or remove things from it, or change any of its elements.

So, the correct way is:

current_list = []
current_list =
  for x <- 1..10, reduce: current_list do
    acc -> [x | acc]
  end

or

current_list = []
current_list = Enum.reduce(1..10, current_list, fn x, acc ->
  [x | acc]
end)
4 Likes

or simply :slight_smile:

current_list = Enum.reduce(1..10, [], fn x, acc ->
  [x | acc]
end)
4 Likes

I guess the author may have put something in the current_list before starting the loop :slight_smile:

If the current_list is initially empty, then Enum.reverse(1..10) should be the shortest code.

1 Like

IMHO, newbies should stay away from if, for or with until being comfortable with functional style programming. They are only syntactical sugar to make things shorter; they don’t provide any vital functionalities.

1 Like

IDK, if is such a common core construct it feels like it would be off-putting to try to learn a lang where that was discouraged. In fact I’d say it’s the ideal candidate from which to learn this functional scoping lesson!

Lexical scoping isn’t limited to functional languages. Many languages use that type of scoping.

Usually people don’t expect do/end blocks to be scope boundries though.

Right! The combination of lexical scoping, expression return values, resulting in do/end variables being local and needing to assign out of them, is what’s the useful take-away.

I think if is great lesson in this, because

  • newcomers will often reach for it early
  • trying to re-assign something out of scope (which is commonly possible in other lexically scoped languages; that’s kind of what I meant by “functional” lexical scoping)
  • possibly discover after the assigning-result refactor that if they want the fallback case to be non-null, they also can return out of else
  • which sets them up for success when the conditional branching becomes more comples and they needbti reach for case; they will have acquired the understanding they need to succeed

A cond do ... will raise if none of the conditions hold true. This is safer for newbies than if that don’t have a else clause.

Yes, though paired with immutability and implicit shadowing it is sometimes a bit weird.

foo = 0
if true, do: foo = 1
IO.puts(foo)

If you do not know about the implicit shadowing, which is dropped again after leaving the ifs lexical scope, then it is not obvious what happens.

The problem is not the scoping, its the shadowing, which is often seen as a bad design decision.

I suppose the compiler could warn on reassignment of shadowed variables. There are legitimate use-cases for it though, especially in more involved conditional bodies. Especially in reduces!

The compiler would certainly warn on the example above because “foo” is unused.

1 Like

That’s true. I suppose the way to capture the anti-shadowing sentiment that this is bad practice would be:

def transform(results) do
  results = if some_check(results) do
    results = Enum.map(&something) #warning: variable used in inner scope shadows outer scope
    use_and_return_interim_results(results)
  else
    []
  end
  use_and_return_ulitimate_results(results)
end

Piping or renaming the variable would fix.

I don’t actually care much for this. It feels like such a warning should exist at the Credo level rather than the compiler, anyways.