Surprising Ruby scoping with while loop - ruby

(1)
a = [1, 2]
while b = a.pop do puts b end
outputs
2
1
(2)
a = [1, 2]
puts b while b = a.pop
results in an error
undefined local variable or method `b'
(3)
b = nil
a = [1, 2]
puts b while b = a.pop
outputs
2
1
What is going on? Why is the scope of b different in #2 than any of the rest?
$ ruby --version
ruby 1.9.3p484 (2013-11-22 revision 43786) [x86_64-linux]
EDIT: Originally I listed irb's behavior as different. It isn't; I was working in a "dirty" session.

Variables are declared to their scope by the lexical parser, which is linear. In while b = a.pop do puts b end, the assignment (b = a.pop) is seen by the parser before the use (puts b). In the second example, puts b while b = a.pop, the use is seen when the definition is still unknown, which produces the error.

The puts statement is executed before the variable 'b' is initially defined, thus resulting in an error.
As a similar example but with an until-statement, consider the following code:
a = [1, 2]
begin
puts "in the block"
end until b = a.pop
Would you expect b to be defined within the block?
Technically the only difference is that until stops on a true return value, while while will continue as long as a.pop returns a true value.
The point in both cases is that b is not in scope until the assignment happened. Right after the assignment, e.g. when the loop returns, b comes available in the current scope. That is called lexical scoping and is how ruby works for local variables like this one.
I found this article to be helpful for understanding scope in ruby.
Update 1: In the previous version of my answer I wrote that this is not comparable to an if. While this is still true, it has nothing to do with the question, which is a simple scope issue.
Update 2: Added a link to some more details explanations regarding scoping in ruby.
Update 3: Removed the first sentence, as it was wrong.

a = [1, 2]
while b = a.pop do puts b end
is same as
a = [1, 2]
b = a.pop
puts b
b = a.pop
puts b
(2)
a = [1, 2]
puts b while b = a.pop
is same as this
a = [1, 2]
puts b
b = a.pop
puts b
b = a.pop
puts b
When b is passed into puts the first time it has not yet been initialized. hence the error message
(3)
b is initialized to nil. even nil is an object in ruby
b = nil
a = [1, 2]
puts b while b = a.pop

Related

What's Python's next() in Ruby?

I am currently learning Ruby and am lost a bit.
This Python snippet:
n = iter([1,2,3,4,5])
for x in n:
print(x)
y = next(n)
print(y)
gives:
1
2
3
4
5
And I'm trying to do the same in Ruby, which is not working:
n = [1,2,3,4,5].each
for x in n do
puts x
y = n.next()
puts y
end
How do I need to write the Python example in Ruby?
You have the right idea except Ruby keeps track of the position of the enumerator internally. Try this:
enum = [1,2,3,4,5].each
#=> #<Enumerator: [1, 2, 3, 4, 5]:each>
loop do
puts enum.next
puts enum.next
end
1
2
3
4
5
Enumerator#next raises a StopInteration exception when invoked after all elements of the enumerator enum have been generated. Kernel#loop handles the exception by breaking out of the loop.
Note that Array#each, without the optional block, has the same effect as Object#to_enum. Also, though authors of Ruby books feel an obligation to cover for loops (seemingly, on page one), they are never used in practice.
In Python the iterator n obtained applying the function iter() can be consumed once.
array = [1,2,3,4,5]
n = iter(array)
for x in n:
print(x)
# 1
# 2
# 3
# 4
# 5
If you then call next(n) you get no output but error: StopIteration. If you try to iterate n again you get no output (no error, the for loop hanldes the exception).
But you can iterate over the array:
for x in array:
print(x)
If you check the output a code like yours, you get:
n = iter([1,2,3,4,5])
for x in n:
print('odd_', x)
y = next(n)
print('even', y)
# odd_ 1
# even 2
# odd_ 3
# even 4
# odd_ 5
# StopIteration
As pointed by #toniedzwiedz Object#to_enum is the equivalent for the Python iter(), so you can call Enumerator#next on it.
In Ruby you can consume more than once the enumerator:
array = [1, 2, 3, 4, 5]
n = array.to_enum
# n = array.each # each returns an Enumerable
for x in n do
puts x
end
for x in n do
puts x
end
Since you can consume more than once the enumerator you get "double" output:
for x in n do
p x
p n.next
end
Or using Array#each:
n.each do |x| # same as array.each.each
p x
p n.next
end
Also in Ruby you reach end the iteration signal:
6.times do
p n.next
end
# `next': iteration reached an end (StopIteration)
You're just trying to print each element of the Array?
[1,2,3,4,5].each {|n| puts n}
If you're looking for a direct equivalent of Python's iterator, you should check out Enumerator#next for a very similar API
arr = [1, 2, 3].to_enum
#<Enumerator: [1, 2, 3]:each>
arr.next
# 1
for x in arr do
puts x
puts arr.next
end
#1
#2
#2
#3
#3
#StopIteration: iteration reached an end
each_cons is what you need. But this method has a downside that is the current will not print last element of the array. Please keep in mind this.
[1,2,3,4,5].each_cons(2) { |current,next_val| puts next_val }

Why can I assign two variables corresponding to an array in Ruby?

After about a year of Ruby, I just saw this somewhere and my mind is blown. Why in the world does this work?
>> words = ['uno', 'dos']
=> ["uno", "dos"]
>> first, second = words
=> ["uno", "dos"]
>> first
=> "uno"
>> second
=> "dos"
Specifically, how does this work:
>> first, second = ['uno', 'dos']
Why can I do this? It makes no syntactical sense!
It makes no syntactical sense
But this is part of Ruby's syntax! In the Ruby docs it is known as array decomposition:
Like Array decomposition in method arguments you can decompose an
Array during assignment using parenthesis:
(a, b) = [1, 2]
p a: a, b: b # prints {:a=>1, :b=>2}
You can decompose an Array as part of a larger multiple assignment:
a, (b, c) = 1, [2, 3]
p a: a, b: b, c: c # prints {:a=>1, :b=>2, :c=>3}
Since each decomposition is considered its own multiple assignment you
can use * to gather arguments in the decomposition:
a, (b, *c), *d = 1, [2, 3, 4], 5, 6
p a: a, b: b, c: c, d: d
# prints {:a=>1, :b=>2, :c=>[3, 4], :d=>[5, 6]}
Edit
as Stefan points out in the comments, the docs don't mention that array decomposition also occurs implicitly (i.e. without parenthesis) if there is only one value on the right-hand side:
a, b = [1, 2] works like (a, b) = [1, 2]
Why can I do this? It makes no syntactical sense!
It makes a perfect sense. It is an example of parallel assignment.
When you use = what is happening is each of the list of variables on the left of = are assigned to each of the list of expressions on the right of =.
first, second = ['uno', 'dos']
# is equivalent to
first, second = 'uno', 'dos'
If there are more variables on the left, than expressions on the right, those left variables are assigned with nil:
first, second = 'uno'
first #=> 'uno'
second #=> nil
As to
words = ['uno', 'dos']
first, second = words
first #=> 'uno'
second #=> 'dos'
It is not assigning the whole words array to first leaving second with nil, because while parallel assignment Ruby tries to decompose the right side expression, and does so if it is an instance of Array.
[TIL] Moreover, it attempts to call to_ary on the right side expression, and if it responds to the method, decomposes accordingly to that object's to_ary implementation (credits to #Stefan):
string = 'hello world'
def string.to_ary; split end
first, second = string
first #=> 'hello'
second #=> 'world'
This is called multiple assignment, handy to assign multiple variables at once.
example
one, two = 1,2
puts one #=>1
puts two #=>2
one, two = [1,2] # this makes sense
one, two = 1 # obviously this doesn't it will assign nil to two
Hope its bit clear now

ruby inline while vs while end

Why does this work:
a = [1, 2, 3]
while n = a.shift
puts n
end
while this doesn't:
a = [1, 2, 3]
puts n while n = a.shift
It works only if I initialize n in advance:
a = [1, 2, 3]
n = nil
puts n while n = a.shift
That is, in general, an interpreter problem, that could not appear in languages with local variable bubbling, like javascript.
The interpreter (reading from left to right) meets right-hand-operand n before any mention of it.
The more I think about it, the more I am convinced it is a bug in ruby interpreter. As #Cary pointed out, the control flow is in fact the same:
a = [2, 3]
n = 1
puts n while n = a.shift
#⇒ 2
#⇒ 3
No trail of 1 in the output above.
n is undefined at the time you attempt the first puts. The condition, and corresponding shift, is only checked after the puts has been evaluated. An alternative which will work as you expected would be
a = [1, 2, 3]
puts a.shift while a.length > 0
Regarding: puts n while n = a.shift,
it will pares puts n first, but n is undefined at that point. Ruby is a dynamically typed language; you don't declare variable type explicitly, but you should assign a value to variables.
For example:
irb(main):027:0> xyz
NameError: undefined local variable or method `xyz' for main:Object
irb(main):028:0> xyz = 1
=> 1

How to declare multiple variables

I want to know how I can declare multiple variables. I typed a,b=1 expecting to get a=1,b=1, but I got:
a,b=1
a #=> 1
b #=> nil
How am I able to do this?
After this code, I did:
a="Hello "
b=a
c="World~"
b << c
b #=> "Hello World"
Why is b the same as a's value?
To declare multiple vars on the same line, you can do that:
a = b = "foo"
puts a # return "foo"
puts b # return "foo" too
About your second question, when doing b << c, you are assigning c's value to b. Then, you are overriding previous value stored in b. Meanwhile, a keeps the same value because Ruby does not user pointers.
What you are doing is called destructuring assignment. Basically, you take what is on the right side of the equals sign, and destructure it, or break it apart, and then assign each section to each corresponding variable on the left.
Ruby is super friendly, and is providing some syntactic sugar that might be confusing.
When you type this:
a, b = 1
You are really saying something closer to this:
[a, b] = [1, nil]
A good example of destructuring assignment can be found here. It's for JavaScript, but I like it because the syntax is very explicit about what is happen when you do such an assigment.
I suppose, in the case of
a, b, c = 1, 2
the runtime system works the following way:
a, b, c = [1, 2]
_result = ( a, b, c = (_values = [1, 2]) )
a = _values[0] # => 1
b = _values[1] # => 2
c = _values[2] # => nil
_result = _values # => [1, 2]
However, in the case of a single value on the right hand side: a, b = 1, the computation process looks a bit different:
_result = ( a, b = ( _value = (_values = [1]).first ) )
a = _values[0] # => 1
b = _values[1] # => nil
_result = _value # => 1
Can someone approve or disprove my assumption?

When do you need to pass arguments to `Thread.new`?

Local variables defined outside of a thread seem to be visible from inside so that the following two uses of Thread.new seem to be the same:
a = :foo
Thread.new{puts a} # => :foo
Thread.new(a){|a| puts a} # => :foo
The document gives the example:
arr = []
a, b, c = 1, 2, 3
Thread.new(a,b,c){|d, e, f| arr << d << e << f}.join
arr #=> [1, 2, 3]
but since a, b, c are visible from inside of the created thread, this should also be the same as:
arr = []
a, b, c = 1, 2, 3
Thread.new{d, e, f = a, b, c; arr << d << e << f}.join
arr #=> [1, 2, 3]
Is there any difference? When do you need to pass local variables as arguments to Thread.new?
When you pass a variable into a thread like that, then the thread makes a local copy of the variable and uses it, so modifications to it do not affect the variable outside of the thread you passed in
a = "foo"
Thread.new{ a = "new"}
p a # => "new"
Thread.new(a){|d| d = "old"}
p a # => "new"
p d # => undefined
I think I hit the actual problem. With a code like this:
sock = Socket.unix_server_socket(SOCK)
sock.listen 10
while conn = sock.accept do
io, address = conn
STDERR.puts "#{io.fileno}: Accepted connection from '#{address}'"
Thread.new{ serve io }
end
it appears to work when accepting few connections. The problem comes when accepting connections quickly one after another. The update to local variable io will be reflected in multiple concurrent threads unless passed as argument to Thread.new

Resources