Anyone who starts to play with Ruby Metaprogramming soon is faced with the Fixnum increment problem.
Fixnum has no pre/post self-increment/decrement operator/method.
Some nice day I innocently tried to do i.succ!, guessing "that obviously exists". I was surprised when I got an error. Well, I thought, in Ruby it's simple: it's just to code:
class Fixnum
def succ!
self.replace succ
end
end
(I'd to use self.replace in String class recently.)
Then I was shocked: replace is not an Object method; it's a String (and some other classes') method, and it's not available to Fixnum (and some other classes).
But then, "how can we (or anyone) implement such methods into Fixnum?" A powerful language like Ruby could not be that restricted!
Since we see many magic codes using Ruby Metaprogramming, I was decided to find a way.
As I was learning Ruby, I understood that if Ruby hasn't something, you can implement it. E.g.: Ruby has not the "with" keyword, like Pascal. That's not a problem, as
you can implement it. So I thought there would be a way to do a self-increment method for Ruby integers.
It isn't really necessary, since we can use i += 1. But then I had adopted the challenge. Now I wouldn't stop anymore. Oh, no!...
One argument for not having the ++ operator in Ruby is: a symbol refers directly to the object, and not to the variable which contains the object. So, if i is 1, trying to do i++ is the same as trying to do 1++, which would turn the value of the number 1 into 2, and 1 woudn't be 1 anymore.
Personally I don't agree with that argument, because you can do "i += 1" and you can't do "1 += 1"; so, I think would be possible to do "i++" not being the same as doing "1++". Anyway I agree with other arguments, like: it's not the Ruby way to code; it can obfuscate the code; it can cause confusion about what is going on in codes like "a[++i] = i++" etc.
I tried many things. The first try that worked was a lambda method to increment a variable through its symbol:
inc = lambda do |x|
eval "#{x} += 1"
end
a = 5
inc[:a]
puts a # => 6
(I'd tried "def inc" to do the same in main:Object and in class Symbol, but inside a "def" eval can't access external variables.)
But that is not like doing "a.inc". So I went on, and on, and on, and some other nice day I finnaly got it! And so I decided to create this blog! (Yes: this post is the reason for which I created this blog!) I used a class which I named "Variable", because it contains the object instead of being the object itself. Here's it:
class Variable
def initialize value = nil
@value = value
end
attr_accessor :value
def method_missing *args, &blk
@value.send(*args, &blk)
end
def to_s
@value.to_s
end
# here's the increment/decrement part
def inc x = 1
@value += x
end
def dec x = 1
@value -= x
end
# pre-increment ".+" when x not present
def +(x = nil)
x ? @value + x : @value += 1
end
def -(x = nil)
x ? @value - x : @value -= 1
end
end
a = Variable.new 5
# normal + operator
puts a + 3 # => 8
puts a # => 5
puts a.inc 2 # => 7
puts a # => 7
puts a.dec # => 6
puts a.dec # => 5
# pre-increment + operator,
# (for those who doesn't like to type "inc")
puts a.+ * 2 # => 12: a turns into 6, which is doubled
puts a # => 6
puts a.value.class # => Fixnum
# puts a while a goes to zero
puts a while a.-> 0
(About the last line, see
more on "goes to" operator. :)
I think it's not soooo useful, but if you can do something interesting with it, please tell me! :)
After doing that, I found
an elegant way to do similar behavior by using delegation.
Enjoy!