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
endAnd 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
#newin Integer - Added tests for
#newin Integer - Made a macro to get Integer from T_INTEGER VALUE
- Added source for
.newmethod in Rational - Added tests for instantiation of Rational
- [ci skip] Added script to debug tests in gdb
- Integer class now supports
2*a&a*2assignments
comments powered by Disqus