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.

And of course the method should be

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

···

On 25/02/2008, Farrel Lifson <farrel.lifson@gmail.com> wrote:

I had a bit of a surprise with the following

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

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

g phil

···

On Mon, Feb 25, 2008 at 07:41:31AM +0900, Farrel Lifson 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

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
-- ezra@engineyard.com
-- EngineYard.com

···

On Feb 24, 2008, at 2:41 PM, Farrel Lifson 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

Farrel Lifson 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?

···

--
Posted via http://www.ruby-forum.com/\.

Ezra Zygmuntowicz 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 Britt

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

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

···

On Mon, Feb 25, 2008 at 12:03 PM, 7stud -- <bbxx789_05ss@yahoo.com> 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?

7stud -- said...

Farrel Lifson 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?

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"

···

--
Cheers,
Marc

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

Arlen Cuss wrote:

···

On Mon, Feb 25, 2008 at 12:03 PM, 7stud -- <bbxx789_05ss@yahoo.com> 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?

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)

James Britt 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.

···

--
Posted via http://www.ruby-forum.com/\.

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

···

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

Assignment is a syntactic construct. "b.bar = 3" is not a method call,
it's an assignment.

Mark Bush wrote:

James Britt 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 Britt

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