eigenclass logo
MAIN  Index  Search  Changes  PageRank  Login

Preincrement plays, not about arithmetics actually

One of the first things people often ask for when coming to Ruby is a ++ operator. The standard answer to that has always been that such a thing makes no sense in Ruby, since you don't want

1++

to turn literal ones into twos*1. But one thing one doesn't hear that often is that preincrement/predecrement operators can be simulated pretty straightforwardly with these semantics:

a = BoxedInteger.new(1)
a += 10
b = a   # => #<BoxedInteger:0xb7ddbd20 @value=11>
a.to_i  # => 11
++a  
a.to_i  # => 12
b.to_i  # => 12

I'll show an example involving arithmetic with boxed integers, but preincrement/decrement should never be used for that. I can only picture this being of use in some DSL*2.

This takes around 30 lines and it's a pretty interesting exercise, so you might want to try to write it on your own before I give my code. Here are some test cases that will tell you when you're done:

require 'test/unit'
class Test_Boxed_Integers < Test::Unit::TestCase
  def setup
    @a = BoxedInteger.new(10)
  end

  def test_unary
    assert_equal(10, (+@a).to_i)
    assert_equal(-10, (-@a).to_i)
  end

  def test_coerce
    assert_equal(10, @a.to_i)
    assert_equal(10, (0 + @a).to_i)
    assert_equal(11, (1 + @a).to_i)
    assert_equal(11, (@a + 1).to_i)
  end
  
  def test_pre_inc
    b = ++@a 
    assert_equal(11, @a.to_i)
    ++b 
    assert_equal(12, b.to_i)
    assert_equal(12, @a.to_i)
  end
  
  def test_pre_dec
    b = --@a 
    assert_equal(9, @a.to_i)
    --b
    assert_equal(8, b.to_i)
    assert_equal(8, @a.to_i)
    ++b
    assert_equal(9, b.to_i)
    assert_equal(9, @a.to_i)
  end

  def test_binary_ops
    b = @a / 10
    assert_equal(100, (@a + 100 * b - @a).to_i)
  end
end

Some spoilers

b = BoxedInteger.new(1)
+b      # => #<PreincBoxedInteger:0xb7ddbbcc @value=1, @boxedint=#<BoxedInteger:0xb7ddbbf4 @value=1>>
++b     # => #<BoxedInteger:0xb7ddbbf4 @value=2>
b       # => #<BoxedInteger:0xb7ddbbf4 @value=2>
-b      # => #<PredecBoxedInteger:0xb7ddba8c @value=-2, @boxedint=#<BoxedInteger:0xb7ddbbf4 @value=2>>
b       # => #<BoxedInteger:0xb7ddbbf4 @value=2>
--b     # => #<BoxedInteger:0xb7ddbbf4 @value=1>
b       # => #<BoxedInteger:0xb7ddbbf4 @value=1>

... what do you know about unary + and #coerce? ...

The code

class BoxedInteger
  def initialize(val); @value = val.to_i end
  %w[+ - * / << >>].each do |meth|
    class_eval <<-EOF, __FILE__, __LINE__ + 1
      def #{meth}(o); self.class.new(@value #{meth} o.to_i) end
    EOF
  end
  def +@; PreincBoxedInteger.new(self) end
  def -@; PredecBoxedInteger.new(self) end
  def coerce(other)
    case other
    when BoxedInteger
      [other, self]
    else
      [BoxedInteger.new(other), self]
    end
  end
  def to_i; @value end
  def replace(value); @value = value; self end
end

class PreincBoxedInteger < BoxedInteger
  def initialize(boxedint)
    super(boxedint.to_i)
    @boxedint = boxedint
  end
  def +@; @boxedint.replace(@boxedint.to_i + 1) end
end

class PredecBoxedInteger < BoxedInteger
  def initialize(boxedint)
    super(-boxedint.to_i)
    @boxedint = boxedint
  end
  def -@; @boxedint.replace(@boxedint.to_i - 1) end
end

Some hints:

  • when you try to do 1 + whatever and Fixnum doesn't know how to handle objects of class whatever.class, it performs whatever.coerce(1) which should return an array with two elements, the first corresponding to '1' and the second to whatever itself. The former receives the #+ message and is given the latter as the argument.
  • unary plus is +@

Dos and donts

Don't use this for actual arithmetics! Only (if at all) in the context of some language where pre-++/-- really makes sense.


testing - mfp (2006-07-28 (Fri) 15:26:41)

trying to save eigenclass from its imminent death (E_NOSPC)

mfp 2006-07-28 (Fri) 15:30:08

and again (seems to be working)


Last modified:2006/07/02 08:56:21
Keyword(s):[blog] [ruby] [frontpage] [preincrement] [predecrement] [++] [--] [coerce] [unary] [operator] [+@] [-@] [technique] [dsl]
References:

*1 another answer would explain that messages are sent to objects, not variables

*2 very much the same way _why made use of unary +/- for filtering rules