Mind Ramblings My Blog

Class Coercion in Ruby C extensions

Hola Everyone! Our classes started this week, and there was less time during the week than I had expected. Anyways, I am at the completion of the Ruby wrappers for the Integer and Rational classes. It would have been complete last week itself if it weren’t for the deadlines I had for registration. Mostly because of the companies that will be coming to the campus this week to select interns. This time I really am going to complete it this weekend for sure.

Class Coercion

I didn’t know of this technique until Isuru suggested me not to modify the existing Ruby classes, and rather have a look at Class Coercion in Ruby. This is an elegant concept to get seamless inter-operation between existing classes and new classes without having to modify the default library code.

My aim was to support operation of SymEngine classes like Integer, Rational and Complex with the existing classes Integer and Rational classes from the Ruby kernel. For that I had supported the operations in the C wrappers. Like, if the other parameter object passed to the method is not of a SymEngine type but a Ruby object, I would first convert it into the corresponding SymEngine object, and then use it for operation.

This supported all the operations of the kind a * 2, where a is an object of the type SymEngine::Integer. But I had to support 2 * a too. That meant (for me) changing the existing class. Overriding all the existing binary operations for it to support SymEngine types, violating the open/closed principle. It was like this

class Fixnum
  alias_method :old_add, :+
  def +(other)
    if other.is_a? SymEngine::Basic
      SymEngine::Integer.new(self) + other
    else
      old_add other
    end
  end

  alias_method :old_sub, :-
  def -(other)
    if other.is_a? SymEngine::Basic
      SymEngine::Integer.new(self) - other
    else
      old_sub other
    end
  end

  alias_method :old_mul, :*
  def *(other)
    if other.is_a? SymEngine::Basic
      SymEngine::Integer.new(self) * other
    else
      old_mul other
    end
  end

  alias_method :old_div, :/
  def /(other)
    if other.is_a? SymEngine::Basic
      SymEngine::Integer.new(self) / other
    else
      old_div other
    end
  end

  alias_method :old_pow, :**
  def **(other)
    if other.is_a? SymEngine::Basic
      SymEngine::Integer.new(self) ** other
    else
      old_pow other
    end
  end
end

And to make it more WET, I had to do the same for the Bignum class.

I had to use aliases to avoid getting in a endless recursive loop, that eventually lead to stack overflow.

The blog post explains the procedure of using Class Coercion in detail. Please give it a read. I would also have updated this post to include my implementation in the Integer and Rational classes.

Next Week

By this time next week, I would have fully supported the Complex class and it’s methods. The RSpec counter-part of the test_arit.py would have been done too.

Commits

  • Configured spec_helper to work with test_unit
  • Added test-unit as development dependency
  • Added script to test with valgrind
  • Added script to test with callgrind tool
  • Updated .gitignore Added notebooks/Gemfile.lock and callgrind generated files
  • Added source for #new in Integer
  • Added tests for #new in Integer
  • Made a macro to get Integer from T_INTEGER VALUE
  • Added source for .new method in Rational
  • Added tests for instantiation of Rational
  • [ci skip] Added script to debug tests in gdb
  • Integer class now supports 2*a & a*2 assignments


comments powered by Disqus