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().
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.
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.
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.
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 ||=.
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.
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"
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?
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.
… 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.
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: