ruby multiple assignment not working as expected - ruby

I am trying to do something like this:
a = b = c = []
a << 1
Now I am expecting that b and c will be an empty array, whereas a will have one element. But its not working like that, here b and c also contains the same element, how is it working like this?

When you do this
a = b = c = []
All three variables point to the same location in memory. They are three references to same location in memory
So when you do
a << 1, you are writing to the memory space referred by all three variables

If you want 3 separate arrays, do:
a, b, c = [], [], []

You can use .dup to create an object with same value at different memory location.
Here is your example without c because it is irrelevant.
irb(main):028:0> a = b = []
=> []
irb(main):029:0> a.object_id #a and b refer to the same location in memory
=> 19502520
irb(main):030:0> b.object_id
=> 19502520
irb(main):031:0> b = a.dup
=> []
irb(main):032:0> b.object_id #b refers to different location in memory
=> 18646920
irb(main):033:0> a << 1
=> [1]
irb(main):034:0> b
=> []

Related

How does Ruby return two values?

Whenever I swap values in an array, I make sure I stored one of the values in a reference variable. But I found that Ruby can return two values as well as automatically swap two values. For example,
array = [1, 3, 5 , 6 ,7]
array[0], array[1] = array[1] , array[0] #=> [3, 1]
I was wondering how Ruby does this.
Unlike other languages, the return value of any method call in Ruby is always an object. This is possible because, like everything in Ruby, nil itself is an object.
There's three basic patterns you'll see. Returning no particular value:
def nothing
end
nothing
# => nil
Returning a singular value:
def single
1
end
x = single
# => 1
This is in line with what you'd expect from other programming languages.
Things get a bit different when dealing with multiple return values. These need to be specified explicitly:
def multiple
return 1, 2
end
x = multiple
# => [ 1, 2 ]
x
# => [ 1, 2 ]
When making a call that returns multiple values, you can break them out into independent variables:
x, y = multiple
# => [ 1, 2 ]
x
# => 1
y
# => 2
This strategy also works for the sorts of substitution you're talking about:
a, b = 1, 2
# => [1, 2]
a, b = b, a
# => [2, 1]
a
# => 2
b
# => 1
No, Ruby doesn't actually support returning two objects. (BTW: you return objects, not variables. More precisely, you return pointers to objects.)
It does, however, support parallel assignment. If you have more than one object on the right-hand side of an assignment, the objects are collected into an Array:
foo = 1, 2, 3
# is the same as
foo = [1, 2, 3]
If you have more than one "target" (variable or setter method) on the left-hand side of an assignment, the variables get bound to elements of an Array on the right-hand side:
a, b, c = ary
# is the same as
a = ary[0]
b = ary[1]
c = ary[2]
If the right-hand side is not an Array, it will be converted to one using the to_ary method
a, b, c = not_an_ary
# is the same as
ary = not_an_ary.to_ary
a = ary[0]
b = ary[1]
c = ary[2]
And if we put the two together, we get that
a, b, c = d, e, f
# is the same as
ary = [d, e, f]
a = ary[0]
b = ary[1]
c = ary[2]
Related to this is the splat operator on the left-hand side of an assignment. It means "take all the left-over elements of the Array on the right-hand side":
a, b, *c = ary
# is the same as
a = ary[0]
b = ary[1]
c = ary.drop(2) # i.e. the rest of the Array
And last but not least, parallel assignments can be nested using parentheses:
a, (b, c), d = ary
# is the same as
a = ary[0]
b, c = ary[1]
d = ary[2]
# which is the same as
a = ary[0]
b = ary[1][0]
c = ary[1][1]
d = ary[2]
When you return from a method or next or break from a block, Ruby will treat this kind-of like the right-hand side of an assignment, so
return 1, 2
next 1, 2
break 1, 2
# is the same as
return [1, 2]
next [1, 2]
break [1, 2]
By the way, this also works in parameter lists of methods and blocks (with methods being more strict and blocks less strict):
def foo(a, (b, c), d) p a, b, c, d end
bar {|a, (b, c), d| p a, b, c, d }
Blocks being "less strict" is for example what makes Hash#each work. It actually yields a single two-element Array of key and value to the block, but we usually write
some_hash.each {|k, v| }
instead of
some_hash.each {|(k, v)| }
tadman and Jörg W Mittag know Ruby better than me, and their answers are not wrong, but I don't think they are answering what OP wanted to know. I think that the question was not clear though. In my understanding, what OP wanted to ask has nothing to do with returning multiple values.
The real question is, when you want to switch the values of two variables a and b (or two positions in an array as in the original question), why is it not necessary to use a temporal variable temp like:
a, b = :foo, :bar
temp = a
a = b
b = temp
but can be done directly like:
a, b = :foo, :bar
a, b = b, a
The answer is that in multiple assignment, the whole right hand side is evaluated prior to assignment of the whole left hand side, and it is not done one by one. So a, b = b, a is not equivalent to a = b; b = a.
First evaluating the whole right hand side before assignment is a necessity that follows from adjustment when the both sides of = have different numbers of terms, and Jörg W Mittag's description may be indirectly related to that, but that is not the main issue.
Arrays are a good option if you have only a few values. If you want multiple return values without having to know (and be confused by) the order of results, an alternative would be to return a Hash that contains whatever named values you want.
e.g.
def make_hash
x = 1
y = 2
{x: x, y: y}
end
hash = make_hash
# => {:x=>1, :y=>2}
hash[:x]
# => 1
hash[:y]
# => 2
Creating a hash as suggested by some is definitely better than array as array indexing can be confusing. When an additional attribute needs to be returned at a certain index, we'll need to make changes to all the places where the return value is used with array.
Another better way to do this is by using OpenStruct. Its advantage over using a hash is its ease of accessibility.
Example: computer = OpenStruct.new(ram: '4GB')
there are multiple ways to access the value of ram
as a symbol key: computer[:ram]
as a string key: computer['ram']
as an attribute(accessor method): computer.ram
Reference Article: https://medium.com/rubycademy/openstruct-in-ruby-ab6ba3aff9a4

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

why does ruby parallel assignment with array of strings returns string

I'm not sure what exactly is going on with the code snippet below.
>> a, b = ["ho", "hey"]
=> ["ho", "hey"]
>> a
=> "ho"
>> b
=> "hey"
>> c, d = "foo", "bar"
=> ["foo", "bar"]
>> c
=> "foo"
>> d
=> "bar"
>> a, b = ["blerg"], ["baz"]
=> [["blerg"], ["baz"]]
>> a
=> ["blerg"]
>> b
=> ["baz"]
Why wouldn't line 1 return a => ["ho"]?
So behind the scenes, what's the difference between these three assignments (a, b = ["ho", "hey"], c, d = "foo", "bar", a, b = ["blerg"], ["baz"])?
a, b = ["ho", "hey"]
a is assigned the first element of the array, which is the string "ho". Nothing weird.
a, b = ["blerg"], ["baz"]
a, b = [["blerg"], ["baz"]]
These two are the same, as you can see by their return values. So a is assigned the first element, which is an array with one element: ["blerg"].
Similarly,
c, d = "foo", "bar"
Is the same as
c, d = ["foo", "bar"]
In Ruby, = takes a list of variables on the left, and a list of expressions on the right. It assigns the the first variable to the value of the first expression, the second variable the value of the second expression, and so on. If there are more variables than expressions, the leftover variables get assigned the value nil.
> a, b, c, d = "foo", 2+3, Array.new(2, 3), [:c, :d]
> a # => "foo"
> b # => 5
> c # => [3, 3]
> d # => [:c, :d]
> e # => nil
There are two exceptions:
Left side has only one variable, right side has multiple expressions
In this case, the above rule would say that the variable just gets set to the value of the first expression on the right. Instead, the variable gets set to the Array consisting of the values of the expression on the right. In the example below, we see a gets the value [:b, :c], instead of just :b:
> a = :b, :c
> a # => [:b, :c]
This is equivalent to:
> *a = :b , :c
> a # => [:b, :c]
The other exception:
The left side has multiple variables, the right side has only one expression and it's an Array
Again, the original rule would imply that the first variable gets set to that whole Array, and the rest of the variables would be nil. However, that Array effectively ends up getting replaced with the list of its elements, and then = reverts to the "default" behaviour described in the beginning:
> a, b, c = [:d, [:e, :f]]
> a # => :d
> b # => [:e, :f]
> c # => nil
This is equivalent to a, b, c = *[:d, [:e, :f]], or just a, b, c = :d, [:e, :f]
In the examples you point out, there are 2 different structures being used on the right hand side of the multiple assignment:
Multiple assignment from a simple array
array = ["item1", "item2"]
a, b = array
# a => "item1"
# b => "item2"
Multiple assignment from an array who's elements are themselves single element arrays (aka: a multi-dimensional array)
array = [["item1"], ["item2"]]
a, b = array
# a => ["item1"]
# b => ["item2"]
Multiple assignment unwraps only the first level of a multi-dimensional array and allows you to assign those elements to multiple variables on the left hand side of the expression. So when you do this with a multi-dimensional array, it takes the first element wich is ["item1"] and assigns in to a, then moves on to the next element, which is ["item2"] and assigns it to b. The multiple assignment doesn't "dig deeper" into the multi-dimensional array, it performs the assignment from the first level of elements.
The assignment operator in the Ruby Language allow Multiple assignments (aka: parallel assignment).
Say lvalue the left side variables or attributes on the left side of the assignment operator (=) and say rvalue the right side values.
By Multiple assignments you can assign one comma delimited list to another, with corresponding variables on the left side getting corresponding values from the right
first parallel assignment
a, b = ["ho", "hey"]
if the last rvalue is an array, you can prefix it with an asterisk, which effectively expands it into its constituent values in place.
the asterisk is not necessary if the rvalue is the only thing on the right-hand side the array will be expanded automatically.
To better understand, try this:
>> a,b,c = "1",*["ho", "hey"]
=> ["1", "ho", "hey"]
>> a
=> "1"
>> b
=> "ho"
>> c
=> "hey"
let's see again:
>> a,b,c = "1",["ho", "hey"]
=> ["1", ["ho", "hey"]]
>> a
=> "1"
>> b
=> ["ho", "hey"]
>> c
=> nil
as you can see, if we don't prefix the array with the * then the array is not expanded.
second parallel assignment
c, d = "foo", "bar"
"foo", "bar" are two literal Strings assigned to the corresponding variable on the left side.
third parallel assignment
a, b = ["blerg"], ["baz"])?
["blerg"] and ["baz"] are two Array each one containing one element of class String;
therefore each "Array of a sigle String" is assigned to the corresponding variable on the left side.

Why does parallel assignment of a single empty array assign multiple nils?

I want to assign an empty array to multiple variables. Here is what I'm doing:
irb(main):015:0> a, b, c = []
=> []
irb(main):016:0> a
=> nil
irb(main):017:0> b
=> nil
irb(main):018:0> c
=> nil
It gives me nil. I wonder why? But if I did this:
irb(main):019:0> a, b, c = [], [], []
=> [[], [], []]
irb(main):020:0> a
=> []
irb(main):021:0> b
=> []
irb(main):022:0> c
=> []
then it works as I expect, but it's a little bit longer than the first one. What's wrong with the first example?
I believe this example will help you understand the problem:
[1] pry(main)> a, b, c = [1,2]
=> [1, 2]
[2] pry(main)> a
=> 1
[3] pry(main)> b
=> 2
[4] pry(main)> c
=> nil
Now back to your problem, you are trying to assign the elements in an empty array to variables, as a result, the three variables all get nil value.
a = b = c = []
but note that all variables will be assigned the same array
so:
a = b = []
b << 1
p b # [1]
p a # [1]
Parallel Assignment With Single Rvalue
If the assignment contains multiple lvalues and one rvalue, the Ruby attempts to expand the rvalue into an array as if you'd called #to_a on the rvalue. So, the problem with your example of a, b, c = [] is that it is semantically equivalent to:
a, b, c = nil.to_a
which would obviously assign nil to the first variable, and leave the others unassigned (which is also nil). In contrast, consider this:
a, b, c = 1
a
# => 1
b
# => nil
c
# => nil
The same principle is at work, but now you can see that the first lvalue does receive an assignment from the right-hand side; it just wasn't obvious when the assignment was nil.
Ruby destructures arrays when you do parallel assignment:
a, b, c = [:foo, :bar, :baz]
a # => :foo
b # => :bar
c # => :baz
If you give it an array with too few entries, it sets the remaining values to nil:
a, b, c = [:foo, :bar]
a # => :foo
b # => :bar
c # => nil
So your first example is just the natural extension of this - an empty array definitely has too few entries to assign any of the variables!

Resources