Class Coercion in Ruby C extensions
25 Jul 2015 GSoC-2015 · SymEngine · RubyHola 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