Suprising behaviour with "def property=" method

I had a bit of a surprise with the following

class Foo
def bar(value)
@bar = value.upcase
end
end

f = Foo.new
my_bar = (f.bar = “bar”)

I initially thought that my_bar would == “BAR” but in fact it equals
“bar”. It seems no matter what the final value of the bar= method is
the return value is always the parameters. First time I’ve run into
this so I thought I would share and ask if anyone can think of anyway
to get around this?

Farrel

This is on 1.8.6 on Gentoo Linux btw.

On 25/02/2008, Farrel L. [email protected] wrote:

I had a bit of a surprise with the following

class Foo
def bar(value)
@bar = value.upcase
end
end

And of course the method should be

def bar=(value)
@bar = value.upcase
end

On Mon, Feb 25, 2008 at 07:41:31AM +0900, Farrel L. wrote:

I initially thought that my_bar would == “BAR” but in fact it equals
“bar”. It seems no matter what the final value of the bar= method is
the return value is always the parameters. First time I’ve run into
this so I thought I would share and ask if anyone can think of anyway
to get around this?

Farrel

my_bar = f.send(:bar=, “bar”)

g phil

On Feb 24, 2008, at 2:41 PM, Farrel L. wrote:

I initially thought that my_bar would == “BAR” but in fact it equals
“bar”. It seems no matter what the final value of the bar= method is
the return value is always the parameters. First time I’ve run into
this so I thought I would share and ask if anyone can think of anyway
to get around this?

Farrel

This is just the way ruby works. It always returns the right hand
side of any method call ending in =. This is for consistency sake.
since you can write methods that look like assignment it keeps things
consistent to always return the rhs of any assignment.

Cheers-

Ezra Z. wrote:

This is just the way ruby works. It *always* returns the right hand 

side of any method call ending in =. This is for consistency sake.

Except that it is inconsistent with the usually true, “The return value
of a method is the lest expression evaluated.”

POLS, yada yada yada. Done deal.

Whatever it’s benefits, though, it adds to list of “This is how Ruby
works, except when it doesn’t.”


James B.

“The greatest obstacle to discovery is not ignorance, but the illusion
of knowledge.”

  • D. Boorstin

Farrel L. wrote:

I had a bit of a surprise with the following

class Foo
def bar(value)
@bar = value.upcase
end
end

f = Foo.new
my_bar = (f.bar = “bar”)

I initially thought that my_bar would == “BAR” but in fact it equals
“bar”. It seems no matter what the final value of the bar= method is
the return value is always the parameters. First time I’ve run into
this so I thought I would share and ask if anyone can think of anyway
to get around this?

Farrel

I’m not sure why the return value of calling bar= is the method
argument, but it makes sense to me that the return value is not the
private instance variable’s value. If the return value were the
private instance variable’s value, that would break the encapsulation
that a class is supposed to provide:

class Dog
def secret_code=(seed)
@secret_code = seed * 10 + 2
end
end

d = Dog.new
return_val = (d.secret_code=(3) )
puts return_val #should this reveal the secret code?

Hold up a second there;

Yes. What would you like it to return? The point is, you’re doing

d.secret_code = 3 – you’re saying the secret code is 3, so actually
setting it to something else is a bit misleading, don’t you think?

I clearly missed the boat here. I didn’t know Ruby always returned the
rval
of the expression.

You learn something new every day. :slight_smile: Sorry for biting.

Arlen

7stud – said…

my_bar = (f.bar = “bar”)
argument, but it makes sense to me that the return value is not the
d = Dog.new
return_val = (d.secret_code=(3) )
puts return_val #should this reveal the secret code?

I concur; the OP’s expected behaviour would break encapsulation.

class Foo
attr_reader :bar
def bar=(value)
@bar = value.upcase
end
end

f = Foo.new
my_bar = (f.bar = “bar”)
puts my_bar # “bar”
puts f.bar # “BAR”

g = Foo.new
g.bar = “bar”
puts g.bar # “BAR”

On Mon, Feb 25, 2008 at 12:03 PM, 7stud – [email protected]
wrote:

class Dog
def secret_code=(seed)
@secret_code = seed * 10 + 2
end
end

d = Dog.new
return_val = (d.secret_code=(3) )
puts return_val #should this reveal the secret code?

Yes. What would you like it to return? The point is, you’re doing
d.secret_code = 3 – you’re saying the secret code is 3, so actually
setting it to something else is a bit misleading, don’t you think?

class Dog
def generate_secret_code(seed)
@secret_code = seed * 10 + 2
self
end
end

This has the fun of allowing method chaining;
d = Dog.new
d.generate_secret_code(3).some_other_method_that_chains.yet_another(some,
args)

Arlen

James B. wrote:

Suppose secret_code=(x) checks that the given value meets some criteria
(say, is a positive int), and if not, uses the value 0. I might want
the method to then return a valid value, not simply what was passed in.

If the setter is called in an assignment context, the assignment will
always return the rhs regardless of what the setter returns. This is
for compatibility as Ezra pointed out.

If you do:
a = b = c = 3
what’s the value of a? You would expect it to be 3.

What about:
a = b.bar = c = 3
What is a now? Again, wouldn’t you still be hoping it would be 3?

Assignment is a syntactic construct. “b.bar = 3” is not a method call,
it’s an assignment. Part of evaluating that assignment involves
invoking a method, but, by definition, assignment returns the rhs, so
whatever happens with any method so invoked, its value is lost.

On Feb 25, 2008, at 2:09 PM, Mark B. wrote:

Assignment is a syntactic construct. “b.bar = 3” is not a method
call,
it’s an assignment.

I would modify that a bit and say that

(b.bar = 3)

is an assignment expression with a side-effect
that happens to be a method call. Consider

(a = 3)

which is also an assignment expression but with
a side-effect of changing the binding of ‘a’.

So the ‘strange’ behavior of setter methods
comes about because of the context in which they
are called, not because they behave differently
from other methods.

The return value of any method can be discarded
in the right context:

c = begin
a # return value discarded
b # return value becomes value of begin/end
end

Gary W.

Arlen C. wrote:

puts return_val #should this reveal the secret code?

Yes. What would you like it to return? The point is, you’re doing
d.secret_code = 3 – you’re saying the secret code is 3, so actually
setting it to something else is a bit misleading, don’t you think?

Actually, you’re saying “Send the message ‘.secret_code=(3)’ to d”

It’s up to d to decide what that means and what happens next.

Suppose secret_code=(x) checks that the given value meets some criteria
(say, is a positive int), and if not, uses the value 0. I might want
the method to then return a valid value, not simply what was passed in.


James B.

“We are using here a powerful strategy of synthesis: wishful thinking.”

  • H. Abelson and G. Sussman
    (in "The Structure and Interpretation of Computer Programs)

Mark B. wrote:

James B. wrote:

Suppose secret_code=(x) checks that the given value meets some criteria
(say, is a positive int), and if not, uses the value 0. I might want
the method to then return a valid value, not simply what was passed in.

If the setter is called in an assignment context, the assignment will
always return the rhs regardless of what the setter returns. This is
for compatibility as Ezra pointed out.

Compatibility with what? Not with how other methods behave.

It’s a cultural thing.

If you do:
a = b = c = 3
what’s the value of a? You would expect it to be 3.

Perhaps. Depends on the language. But let’s assume so.

What about:
a = b.bar = c = 3
What is a now? Again, wouldn’t you still be hoping it would be 3?

No.

Depends on b. Maybe 3 is not a valid argument for whatever bar does.

I expect that if I’m involving an object then that object may have its
own ideas about how to handle the request. That’s sort of their job.

Assignment is a syntactic construct.

Assignment creates or changes a variable binding.

“b.bar = 3” is not a method call,
it’s an assignment.

b.methods.include?( ‘bar=’ )

def b.bar=( arg )
exit if arg % 2 == 0
end

Pedantic, sure, but believing that “=” always means a setter method, or
is altering an attribute named in the message, is a mistake. It’s
seems mainly to be a convenience for people who like to think in terms
of getters and setters rather than pure messages and private attributes.

It’s handy, but a little heavy-handed.

Here’s a less churlish example:

def b.bar=(x)
@bar = x.to_i.abs
end

Sadly, this will not return the value of @bar.

Most people don’t have a problem with this, and I suspect that was a
reason matz made Ruby behave this way. But it seems to impede a broader
understanding of how Ruby works (and maybe that is part of the reason
some folks misuse, for example, open classes.)

It’s a trade-off.

Part of evaluating that assignment involves
invoking a method, but, by definition,

The definition comes first, and can be otherwise.

assignment returns the rhs, so
whatever happens with any method so invoked, its value is lost.

As so behaviorally defined in Ruby for methods of a certain form. It
breaks the simpler concept of all object interaction being done with
messages, and all methods returning the value of the last executed
expression.

But, again, this is PO(matz)LS. I think I understand why matz choose
it, but it was a matter of taste, not immutable laws of computer
science.


James B.

“The use of anthropomorphic terminology when dealing with
computing systems is a symptom of professional immaturity.”

  • Edsger W. Dijkstra