||= with 1.8 and 1.9?

A friend of mine on Twitter recently posted this tidbit of code:

class OrOrEquals
def test
@test
end

def test=(test)
@test = test
‘not test’
end
end

p (OrOrEquals.new.test = ‘test’)

ruby 1.8 returns ‘test’

ruby 1.9 returns ‘test’

p (OrOrEquals.new.test ||= ‘test’)

ruby 1.8 returns ‘test’

ruby 1.9 returns ‘not test’

It works as indicated. Is this -normal- behavior ?

Aldric G. wrote:

It works as indicated. Is this -normal- behavior ?

FWIW, I get identical results to you using

ruby 1.8.6 (2007-09-24 patchlevel 111) [i486-linux]
ruby 1.9.2dev (2009-07-18 trunk 24186) [i686-linux]

In ruby 1.8, an attribute writer method (:foo=) always(*) returns the
value of its argument, rather than the last value calculated in the
method, so that you can tell by inspection that

a = foo.bar = 123

will set a to 123. Perhaps it has been decided to change that for 1.9.

(*) Strangely, even 1.8 doesn’t work that way if you use send().

RUBY_VERSION
=> “1.8.6”

class Foo; def bar=(x); 123; end; end
=> nil

Foo.new.bar = 999
=> 999

Foo.new.send(:bar=, 999)
=> 123

On Wed, Aug 26, 2009 at 11:08 AM, Brian C.[email protected]
wrote:

In ruby 1.8, an attribute writer method (:foo=) always(*) returns the
value of its argument, rather than the last value calculated in the
method, so that you can tell by inspection that

a = foo.bar = 123

actually it’s not that an attribute writer method always returns the
argument it’s that a chained assignment

a = b = c

is treated the same as

b = c; a = c

in other words the result of the b= is ignored.

That’s why sending :bar= is giving the result of the method.

As for why Ruby1.9 is treating the ||= form differently, I haven’t
figured that out.


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale

Brian C. wrote:

Aldric G. wrote:

It works as indicated. Is this -normal- behavior ?

FWIW, I get identical results to you using

ruby 1.8.6 (2007-09-24 patchlevel 111) [i486-linux]
ruby 1.9.2dev (2009-07-18 trunk 24186) [i686-linux]

$ r -v
ruby 1.8.6 (2007-03-13 patchlevel 0) [i686-darwin8.11.1]
$ r9 -v
ruby 1.9.1p243 (2009-07-16 revision 24175) [i386-darwin8.11.1]

$ r r1test.rb
“test”
“test”

$ r9 r1test.rb
“test”
“not test”

In ruby 1.8, an attribute writer method (:foo=) always(*) returns the
value of its argument, rather than the last value calculated in the
method, so that you can tell by inspection that

a = foo.bar = 123

will set a to 123. Perhaps it has been decided to change that for 1.9.

If so, then David Black didn’t know about it:


WARNING Setter methods don’t return what you might think. When you
use the syntactic sugar that lets you make calls to = methods look like
assignments, Ruby takes the assignment semantics seriously. Assignments
(like x = 1) evaluate to whatever’s on their right-hand side. [On the
other hand,] [m]ethods usually return the value of the last expression
evaluated during execution. But = method calls behave like assignments:
the value of the expression ticket.price = 63.00 is 63.00, even if the
ticket= method returns the string “Ha ha!” The idea is to keep the
semantics consistent. Under the hood, it’s a method call; but it looks
like an assignment and behaves like an assignment with respect to its
value as an expression.

p. 72, The Well Grounded Rubyist


A word on Ruby versions

The Well Grounded Rubyist is about version 1.9.1 of the Ruby language,
the most recent version.

p xxvii, The Well Grounded Rubyist

On Wed, Aug 26, 2009 at 12:15 PM, 7stud –[email protected]
wrote:

ticket= method returns the string “Ha ha!” The idea is to keep the
semantics consistent. Under the hood, it’s a method call; but it looks
like an assignment and behaves like an assignment with respect to its
value as an expression.

p. 72, The Well Grounded Rubyist

I know what David is getting at here, but that first sentence isn’t
exactly true, although I’ll grant a pedagogical license for it.

It’s not that the return value isn’t what you think, it’s that it’s
ignored when the setter method is called from the sugary syntax of an
assignment. Note that he says that ticket.price = 63.00 evaluates to
63.00 "even if the ticket= method returns the string “Ha Ha!”. Which
is different than saying ticket= returns 63.00 instead of “what you
might think.”

And Brian’s example of using send demonstrates that it’s the
assignment and not the method call result which isn’t what you think.


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale

Rick Denatale wrote:

On Wed, Aug 26, 2009 at 12:15 PM, 7stud –[email protected]
wrote:

ticket= method returns the string “Ha ha!” �The idea is to keep the
semantics consistent. �Under the hood, it’s a method call; but it looks
like an assignment and behaves like an assignment with respect to its
value as an expression.

p. 72, The Well Grounded Rubyist

I know what David is getting at here, but that first sentence isn’t
exactly true, although I’ll grant a pedagogical license for it.

It’s not that the return value isn’t what you think, it’s that it’s
ignored when the setter method is called from the sugary syntax of an
assignment.

It sounds like you are trying to make a distinction between the sugared
syntax and the normal method call syntax, but…

class A
def x
@x
end

def x=(val)
@x = val
“Ha, ha!”
end
end

a1 = A.new
puts a1.x = 10

a2 = A.new
puts a2.x=(10)

–output:–
10
10

both versions return the same thing.

Note that he says that ticket.price = 63.00 evaluates to
63.00 "even if the ticket= method returns the string “Ha Ha!”. Which
is different than saying ticket= returns 63.00 instead of “what you
might think.”

So are you faulting his use of the phrase "returns the string “Ha, ha!”
because the method doesn’t actually return “Ha, ha!”–it returns 63.00?

The issue is with the ||= operator though! Did the way it is use change,
from doing an assignment to sending a value to the ‘assign’ method, or
something like that?

I mean, ||= is / was understood to be an assignment operator, wasn’t it?
Did that change?

In ruby 1.8, an attribute writer method (:foo=) always(*) returns the
value of its argument, rather than the last value calculated in the
method, so that you can tell by inspection that

a = foo.bar = 123

will set a to 123. Perhaps it has been decided to change that for 1.9.

If so, then David Black didn’t know about it:

Whoops. I didn’t see the change from = to ||= in the code. So nothing
has changed from 1.8.6 to 1.9.1 with respect to = methods. The change
is in the behavior of ||=. Those quotes from the Well
Grounded Rubyist don’t apply to ||=.

Hi –

On Thu, 27 Aug 2009, 7stud – wrote:

I know what David is getting at here, but that first sentence isn’t
def x
puts a1.x = 10

Note that he says that ticket.price = 63.00 evaluates to
63.00 "even if the ticket= method returns the string “Ha Ha!”. Which
is different than saying ticket= returns 63.00 instead of “what you
might think.”

So are you faulting his use of the phrase "returns the string “Ha, ha!”
because the method doesn’t actually return “Ha, ha!”–it returns 63.00?

I think that’s the sentence he feels I got right, as opposed to the
first one. His point is that you can use send to show what the method
is actually returning:

def x=(y); “hi”; end
=> nil

self.x = 1
=> 1

send(:x=,1)
=> “hi”

So that my first sentence should be “Calls to setter methods don’t
always produce the results you might think they would” or something
like that.

David

At 2009-08-26 11:00AM, “Aldric G.” wrote:

end
It works as indicated. Is this -normal- behavior ?
Not only that, this odd behaviour only happens for ||= and not for plain
=

$ cat orequal.rb
puts RUBY_VERSION

class OrOrEquals
  def test
    @test
  end

  def test=(test)
    @test = test
    'not test'
  end
end

o = OrOrEquals.new
p o.test = 'a test'
o.test = nil
p o.test ||= 'a test'


$ ruby orequal.rb
1.8.7
"a test"
"a test"

$ ruby19 orequal.rb
1.9.1
"a test"
"not test"

On Wed, Aug 26, 2009 at 12:49 PM, 7stud –[email protected]
wrote:

I know what David is getting at here, but that first sentence isn’t
exactly true, although I’ll grant a pedagogical license for it.

It’s not that the return value isn’t what you think, it’s that it’s
ignored when the setter method is called from the sugary syntax of an
assignment.

It sounds like you are trying to make a distinction between the sugared
syntax and the normal method call syntax, but…

a1 = A.new
puts a1.x = 10

a2 = A.new
puts a2.x=(10)

both versions return the same thing.

Because they are both using the assignment sugar. Parenthesizing the
10 doesn’t change that.

Note that he says that ticket.price = 63.00 evaluates to
63.00 "even if the ticket= method returns the string “Ha Ha!”. Â Which
is different than saying ticket= returns 63.00 instead of “what you
might think.”

So are you faulting his use of the phrase "returns the string “Ha, ha!”
because the method doesn’t actually return “Ha, ha!”–it returns 63.00?

David already answered this one, he understood.


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale

Rick Denatale wrote:

As for why Ruby1.9 is treating the ||= form differently, I haven’t
figured that out.

I always thought that a ||= b was just a shortcut for a = a || b, but it
looks like that’s not necessarily true in 1.9.

Rick Denatale wrote:

a1 = A.new
puts a1.x = 10

a2 = A.new
puts a2.x=(10)

both versions return the same thing.

Because they are both using the assignment sugar. Parenthesizing the
10 doesn’t change that.

The method name is ‘x=’ and it was called using ‘obj.x=’. Where’s the
sugar?

7stud – wrote:

Rick Denatale wrote:

a1 = A.new
puts a1.x = 10

a2 = A.new
puts a2.x=(10)

both versions return the same thing.

Because they are both using the assignment sugar. Parenthesizing the
10 doesn’t change that.

The method name is ‘x=’ and it was called using ‘obj.x=’. Where’s the
sugar?

The parentheses in (10) are not delimiting arguments of a method call;
they are just bracketing a value expression. Note:

class Foo; def bar=(x,y,z); 123; end; end
=> nil

Foo.new.send(:bar=,7,8,9)
=> 123

Foo.new.bar=(7,8,9)
SyntaxError: compile error
(irb):3: syntax error, unexpected ‘,’, expecting ‘)’
Foo.new.bar=(7,8,9)
^
from (irb):3
from :0

Similarly:

Foo.new.bar= 7,8,9
ArgumentError: wrong number of arguments (1 for 3)
from (irb):4:in `bar=’
from (irb):4
from :0

The other way you can see it’s sugar is that a space is allowed between
x and =.

Joel VanderWerf wrote:

h = Hash.new(0)
h[1] ||= 2 # this…
h[3] = h[3] || 4 # is not like this…
h[5] || h[5] = 6 # but more like this.
p h # {3=>0}

Hashes have always been a kind of special case, especially when they
have a default value set up :slight_smile: We’re talking about assignments on plain
variables, which are (I thought) more clear-cut.

Aldric G. wrote:

have a default value set up :slight_smile: We’re talking about assignments on plain
variables, which are (I thought) more clear-cut.

True. The quirk happens with reader and writer methods as well as hashes
(scroll down a bit in the google groups page to see some examples).

Brian C. wrote:

I always thought that a ||= b was just a shortcut for a = a || b, but it
looks like that’s not necessarily true in 1.9.

It’s never been exactly the same in 1.8, either.

h = Hash.new(0)
h[1] ||= 2 # this…
h[3] = h[3] || 4 # is not like this…
h[5] || h[5] = 6 # but more like this.
p h # {3=>0}

http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/47f273f6ab57303e/291babfc79b4b822

Joel VanderWerf wrote:

True. The quirk happens with reader and writer methods as well as hashes
(scroll down a bit in the google groups page to see some examples).

Look at the original code, though…

class OrOrEquals
attr_accessor :test
def test
@test
end

def test=(test)
@test = test
‘not test’
end
end

p (OrOrEquals.new.test = ‘test’)

ruby 1.8 returns ‘test’

ruby 1.9 returns ‘test’

p (OrOrEquals.new.test ||= ‘test’)

ruby 1.8 returns ‘test’

ruby 1.9 returns ‘not test’

… And compare this to what’s written in that g-group:

"I also thought so, but, according to “The ruby programming language”,
it’s an
exception. It states that, if the left hand of var ||= something is not
nil or
false, no assignment is performed and, if the left hand is an array
element or
attribute, the setter method is not called. "

Well, then… It -IS- assigning something, isn’t it, since there’s no
value to @test ? Why does it return something other than the result of
the assignment?

On Wed, Aug 26, 2009 at 3:46 PM, Rick DeNatale[email protected]
wrote:

Right, the question is why in Ruby 1.9 is using the ‘not a test’
returned from the setter method in the case of ||= unlike Ruby 1.8.x
and like itself in the = case:

According the rubyspec project:

language/variables_spec.rb:
describe “Operator assignment ‘var op= expr’” do
it “is equivalent to ‘var = var op expr’” do
x = nil
(x ||= 17).should == 17
x.should == 17
(x ||= 2).should == 17
x.should == 17
describe “Operator assignment ‘obj.meth op= expr’” do
it “is equivalent to ‘obj.meth = obj.meth op expr’” do
@x.a = nil
(@x.a ||= 17).should == 17
@x.a.should == 17
(@x.a ||= 2).should == 17
@x.a.should == 17
describe “Operator assignment ‘obj[idx] op= expr’” do
it “is equivalent to ‘obj[idx] = obj[idx] op expr’” do
x = [1,nil,12]
(x[1] ||= 17).should == 17
x.should == [1,17,12]
(x[1] ||= 2).should == 17
x.should == [1,17,12]
it “returns result of rhs not result of []=” do
a = VariablesSpecs::Hashalike.new
(a[123] = 2).should == 2
(a[123] ||= 2).should == 123
(a[nil] ||= 2).should == 2

The specs don’t address the case of a ‘hijacked’ setter, but they do
give the impression that the ruby1.9 behaviour is incorrect.

I’ve raised this on ruby-core. I’m interested to see what Matz & co
have to say.

Yes, should be interesting.

On Wed, Aug 26, 2009 at 2:49 PM, Aldric G.[email protected]
wrote:

Joel VanderWerf wrote:

Well, then… It -IS- assigning something, isn’t it, since there’s no
value to @test ? Why does it return something other than the result of
the assignment?

Right, the question is why in Ruby 1.9 is using the ‘not a test’
returned from the setter method in the case of ||= unlike Ruby 1.8.x
and like itself in the = case:

To make it a bit clearer by moving the assignments out of the p calls:

puts RUBY_VERSION

class OrOrEquals
def test
@test
end

def test=(test)
@test = test
‘not test’
end
end

o = OrOrEquals.new

direct = o.test = ‘a test’
p direct
o.test = nil
with_oror = o.test ||= ‘a test’
p with_oror
p o.test
o.test = nil

$ multiruby or_or_equals.rb
/opt/local/lib/ruby/site_ruby/1.8/rubygems/source_index.rb:144:
warning: /Users/rick/.gem/ruby/1.8/specifications: Permission denied

VERSION = 1.8.6-p368
CMD = ~/.multiruby/install/1.8.6-p368/bin/ruby or_or_equals.rb

1.8.6
“a test”
“a test”
“a test”

RESULT = 0

VERSION = 1.8.7-p160
CMD = ~/.multiruby/install/1.8.7-p160/bin/ruby or_or_equals.rb

1.8.7
“a test”
“a test”
“a test”

RESULT = 0

VERSION = 1.9.1-p0
CMD = ~/.multiruby/install/1.9.1-p0/bin/ruby or_or_equals.rb

1.9.1
“a test”
“not test”
“a test”

I’ve raised this on ruby-core. I’m interested to see what Matz & co
have to say.


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale