||= with 1.8 and 1.9 ?

A

Aldric Giacomoni

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 ?
 
B

Brian Candler

Aldric said:
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().
 
7

7stud --

Brian said:
Aldric said:
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.
 
R

Rick DeNatale

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

=A0 a =3D foo.bar =3D 123

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


a =3D b =3D c

is treated the same as

b =3D c; a =3D c

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

That's why sending :bar=3D is giving the result of the method.

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



--=20
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
 
7

7stud --

In ruby 1.8, an attribute writer method :)foo=) always(*) returns the
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 ||=.
 
R

Rick DeNatale

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

-------
WARNING =A0 Setter methods don't return what you might think. =A0When you
use the syntactic sugar that lets you make calls to =3D methods look like
assignments, Ruby takes the assignment semantics seriously. =A0Assignment= s
(like x =3D 1) evaluate to whatever's on their right-hand side. =A0[On th= e
other hand,] [m]ethods usually return the value of the last expression
evaluated during execution. =A0But =3D method calls behave like assignmen= ts:
the value of the expression ticket.price =3D 63.00 is 63.00, even if the
ticket=3D method returns the string "Ha ha!" =A0The idea is to keep the
semantics consistent. =A0Under 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.

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 =3D 63.00 evaluates to
63.00 "even if the ticket=3D method returns the string "Ha Ha!". Which
is different than saying ticket=3D 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.


--=20
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
 
7

7stud --

Rick said:
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?
 
A

Aldric Giacomoni

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?
 
D

David A. Black

Hi --

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.



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:
=> "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

--
David A. Black / Ruby Power and Light, LLC / http://www.rubypal.com
Q: What's the best way to get a really solid knowledge of Ruby?
A: Come to our Ruby training in Edison, New Jersey, September 14-17!
Instructors: David A. Black and Erik Kastner
More info and registration: http://rubyurl.com/vmzN
 
R

Rick DeNatale

It sounds like you are trying to make a distinction between the sugared
syntax and the normal method call syntax, but...
a1 =3D A.new
puts a1.x =3D 10

a2 =3D A.new
puts a2.x=3D(10)
both versions return the same thing.

Because they are both using the assignment sugar. Parenthesizing the
10 doesn't change that.
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.


--=20
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
 
7

7stud --

Rick said:
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?
 
B

Brian Candler

Rick said:
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.
 
B

Brian Candler

7stud said:
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:
SyntaxError: compile error
(irb):3: syntax error, unexpected ',', expecting ')'
Foo.new.bar=(7,8,9)
^
from (irb):3
from :0
Similarly:
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 =.
 
A

Aldric Giacomoni

Joel said:
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 :) We're talking about assignments on plain
variables, which are (I thought) more clear-cut.
 
J

Joel VanderWerf

Aldric said:
Joel said:
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 :) 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).
 
A

Aldric Giacomoni

Joel said:
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?
 
R

Rick DeNatale

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
 
B

brabuhr

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.
 
D

David A. Black

Hi --

According the rubyspec project:

language/variables_spec.rb:
describe "Operator assignment 'var op= expr'" do
it "is equivalent to 'var = var op expr'" do

It isn't, though, at least in the ||= case (see Joel W.'s post).
Matz's characterization of it to me at RubyConf (or somewhere) was:

x ||= y same as x || x = y


David

--
David A. Black / Ruby Power and Light, LLC / http://www.rubypal.com
Q: What's the best way to get a really solid knowledge of Ruby?
A: Come to our Ruby training in Edison, New Jersey, September 14-17!
Instructors: David A. Black and Erik Kastner
More info and registration: http://rubyurl.com/vmzN
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,169
Latest member
ArturoOlne
Top