Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic way to replace an object in it's own method

Tags:

ruby

With strings one can do this:

a = "hello"
a.upcase!
p a #=> "HELLO"

But how would I write my own method like that?

Something like (although that doesn't work obviously):

class MyClass
  def positify!
    self = [0, self].max
  end
end

I know there are some tricks one can use on String but what if I'm trying to do something like this for Object?

like image 739
Jakub Hampl Avatar asked Feb 03 '12 15:02

Jakub Hampl


4 Answers

Many classes are immutable (e.g. Numeric, Symbol, ...), so have no method allowing you to change their value.

On the other hand, any Object can have instance variables and these can be modified.

There is an easy way to delegate the behavior to a known object (say 42) and be able to change, later on, to another object, using SimpleDelegator. In the example below, quacks_like_an_int behaves like an Integer:

require 'delegate'
quacks_like_an_int = SimpleDelegator.new(42)
quacks_like_an_int.round(-1) # => 40
quacks_like_an_int.__setobj__(666)
quacks_like_an_int.round(-1) # => 670

You can use it to design a class too, for example:

require 'delegate'
class MutableInteger < SimpleDelegator
  def plus_plus!
    __setobj__(self + 1)
    self
  end

  def positify!
    __setobj__(0) if self < 0
    self
  end
end

i = MutableInteger.new(-42)
i.plus_plus! # => -41
i.positify! # => 0
like image 167
Marc-André Lafortune Avatar answered Nov 13 '22 01:11

Marc-André Lafortune


Well, the upcase! method doesn't change the object identity, it only changes its internal structure (s.object_id == s.upcase!.object_id).

On the other hand, numbers are immutable objects and therefore, you can't change their value without changing their identity. AFAIK, there's no way for an object to self-change its identity, but, of course, you may implement positify! method that changes properties of its object - and this would be an analogue of what upcase! does for strings.

like image 41
Alexis Avatar answered Nov 13 '22 00:11

Alexis


Assignment, or binding of local variables (using the = operator) is built-in to the core language and there is no way to override or customize it. You could run a preprocessor over your Ruby code which would convert your own, custom syntax to valid Ruby, though. You could also pass a Binding in to a custom method, which could redefine variables dynamically. This wouldn't achieve the effect you are looking for, though.

Understand that self = could never work, because when you say a = "string"; a = "another string" you are not modifying any objects; you are rebinding a local variable to a different object. Inside your custom method, you are in a different scope, and any local variables which you bind will only exist in that scope; it won't have any effect on the scope which you called the method from.

like image 2
Alex D Avatar answered Nov 12 '22 23:11

Alex D


You cannot change self to point to anything other than its current object. You can make changes to instance variables, such as in the case string which is changing the underlying characters to upper case.

As pointed out in this answer: Ruby and modifying self for a Float instance

There is a trick mentioned here that is a work around, which is to write you class as a wrapper around the other object. Then your wrapper class can replace the wrapped object at will. I'm hesitant on saying this is a good idea though.

like image 2
Kelend Avatar answered Nov 13 '22 01:11

Kelend