Update :queue in loop

Hi All,
I’ve tied update queue in loop but I failed,

Why these snippets don’t work?

q = :queue.new
~w/one two three four/ |> Enum.each(fn w -> q= :queue.in(w, q) end)
q
{[], []}


for val <- ~w/one two three four/ do 
   q = :queue.in(val ,q); 
end    

The reason is that Elixir uses matching instead of assignment. This is a consequence of being a language where all datatypes are immutable.
Essentially, variables should be seen as ‘labels you can stick on a certain value’ rather than ‘boxes you can put a value in’. You can move variables to new values but this will not change the values but rather only what the variable refers to in the current scope.
This means in your example that whenever you write q = something, you shadow the outer variable q, rather than override the value stored in (the outer) q.

Instead, the only way to extract a value from some inner context is by returning it.
For this example, use Enum.reduce instead:

q = :queue.new

~w{one two three four}
|> Enum.reduce(q, fn element, queue -> 
  :queue.in(element, queue) 
end)
4 Likes

Elixir has rebindable variables, not mutable variables. The difference isn’t noticeable in straight-line code:

a = 1
a = 2
# a has the value 2 now

but it’s important in code with nested scopes:

foo = 1

if 1 > 0 do
  foo = 2
  # foo has the value 2 here
end
# foo retains the original value 1 here

Inside the nested scope (the do/end block, or a fn body) the name can be rebound to a different value, but that doesn’t alter the binding in the surrounding scope. It’s why you see this pattern a lot in Elixir:

some_var = params["from_the_user"]
...
some_var =
  if something_conditional?(some_var) do
    "other value"
  else
    some_var
  end

# versus this, which DOES NOT WORK and gives a compiler warning
some_var = params["from_the_user"]

if some_conditional?(some_var) do
  # rebinds some_var but only inside the block
  some_var = "other value"
end

When you need to accumulate data while iterating, that’s usually a sign you need Enum.reduce or similar functions. For instance, this produces the queue your examples were trying to get:

Enum.reduce(~w/one two three four/, :queue.new, fn val, q -> :queue.in(val, q) end)
3 Likes

Elixir is a lexically scoped language like many others. Therefore this works:

a = 1
Enum.map([1, 2, 3], fn x -> x + a end)
# [2, 3, 4]

The only difference in elixir is that re-assignment works different than with other languages. Each scope has it’s own set of bound variables. If you now reference a variable elixir will first look for a variables bound to the current scope and if nothing was found fall back to outer scopes. When assigning a variable though they can only be assigned to the current scope and never leak into outer scopes. So:

a = 1
Enum.map([1, 2, 3], fn x -> 
  # a not bound in scope, but out of scope
  a = a + 1 # the value of a from outer scope + 1 is bound to a in scope
  x + a # Essentially x + 2 for each invokation
end)
# [3, 4, 5]

This actually has nothing to do with data being immutable in elixir. Early versions of elixir did support the following, but those inconsistencies have since been fixed.

a = 1
if true do
  a = 2
end
# a == 2 in early elixir versions, a == 1 today

The reason for elixir to work like that, is that it’s a language based on expressions instead of statements. Everything in elixir returns a value – yes things like if or for as well – which allows for the limitation of assignment to not bubble up the current scope, but simply using return values for that. This usually results in cleaner code, as you only need to be aware of the current scope when it comes to assigning variables.

2 Likes

Thanks for awesome explanations! :slight_smile:

1 Like