views:

223

answers:

3

Let's say I have a Ruby class:

class MyClass

 def self.property
  return "someVal"
 end

 def self.property=(newVal)
  # do something to set "property"
  success = true

  return success       # success is a boolean
 end

end

If I try and do MyClass.property=x, the return value of the whole statement is always x. It is a convention in a lot of C-based/inspired languages to return a boolean "success" value - is it possible to do this for a setter using the "equals syntax" in Ruby?

Furthermore - if this isn't possible, why not? Is there any conceivable downside to allowing an "equals setter" operation return a value?

+3  A: 

I'm not a Ruby expert but I'd say no for that case I'm afraid. A property setter is solely there to set the value of a private field, not to have any side effects like returning result codes.

If you want that functionality then forget the setter and write a new method called TrySetProperty or something which tries to set the property and returns a boolean.

Mark Pim
+3  A: 

One downside is that you would break the chained assignment semantics:

$ irb 
irb(main):001:0> x = y = 3
=> 3
irb(main):002:0> p x
3
=> nil
irb(main):003:0> p y
3
=> nil
irb(main):004:0>

Consider:

x = MyClass.property = 3

Then x would take true if this worked as you had expected (right-associativity). That could be a surprise for people using your interface and used to the typical semantics.

You also got me thinking about parallel assignment, eg:

x, y = 1, 2

Apparently the return value from that expression is implementation specific... I guess I won't be chaining parallel assignments :)

Nice question!

Martin Carpenter
+2  A: 

Like Martin says, this would break assignment chaining.

The way ruby assignment methods are defined to work expands MyClass.property = 3 to the equivalent of (lambda { |v| MyClass.send('property=', v); v })[3] (not really, but this shows how chaining works). The return value of the assignment is always the value assigned.

If you want to see the result of your MyClass#property= method, then use #send:

irb> o = Object.new
=> #<Object:0x15270>
irb> def o.x=(y)
irb>   @x = y+1
irb>   puts "y = #{y}, @x = #@x"
irb>   true
irb> end
=> nil
irb> def o.x
irb>   puts "@x = #@x"
irb>   @x
irb> end
=> nil
irb> o.x = 4
y = 4, @x = 5
=> 4
irb> o.x
@x = 5
=> 5
irb> o.send('x=', 3)
y = 3, @x = 4
=> true

However, the ruby way to do this is with exceptions - if something goes wrong during the assignment, raise an exception. Then all invokers must handle it if something goes wrong, unlike a return value, which can be easily ignored:

# continued from above...
irb> def o.x=(y)
irb>   unless y.respond_to? :> and (y > 0 rescue false)
irb>     raise ArgumentError, 'new value must be > 0', caller
irb>   end
irb>   @x = y + 1
irb>   puts "y = #{y}, @x = #@x"
irb> end
=> nil
irb> o.x = 4
y = 4, @x = 5
=> 4
irb> o.x = 0
ArgumentError: new value must be > 0
    from (irb):12
    from :0
irb> o.x = "3"
ArgumentError: new value must be > 0
    from (irb):13
    from :0
irb> o.x
@x = 5
=> 5
rampion