Suprising behaviour with "def property=" method

F

Farrel Lifson

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
 
F

Farrel Lifson

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
 
P

Philipp Hofmann

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


my_bar = f.send:)bar=, "bar")

g phil
 
E

Ezra Zygmuntowicz

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 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 Zygmuntowicz
-- Founder & Software Architect
-- (e-mail address removed)
-- EngineYard.com
 
7

7stud --

Farrel said:
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?
 
J

James Britt

Ezra said:
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 Britt

"The greatest obstacle to discovery is not ignorance, but the illusion
of knowledge."
- D. Boorstin
 
A

Arlen Cuss

[Note: parts of this message were removed to make it a legal post.]

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
 
A

Arlen Cuss

[Note: parts of this message were removed to make it a legal post.]

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. :) Sorry for biting.

Arlen
 
M

marc

7stud -- said...
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?

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"
 
J

James Britt

Arlen said:
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 Britt

"We are using here a powerful strategy of synthesis: wishful thinking."
- H. Abelson and G. Sussman
(in "The Structure and Interpretation of Computer Programs)
 
M

Mark Bush

James said:
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.
 
G

Gary Wright

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 Wright
 
J

James Britt

Mark said:
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 Britt

"The use of anthropomorphic terminology when dealing with
computing systems is a symptom of professional immaturity."
- Edsger W. Dijkstra
 

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,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top