Duck Type in JRuby Extensions

Sometimes it would be nice to use duck typing to inter-convert between different vector types, that seem to proliferate in the processing environment. It is for this reason I’m experimenting with duck typing in Vec2D and Vec3D constructors:-

Vec2D

/**
 *
 * @param context ThreadContext
 * @param klazz IRubyObject
 * @param args optional (no args jx = 0, jy = 0)
 * @return new Vec2 object (ruby)
 */
@JRubyMethod(name = "new", meta = true, rest = true)
public static final IRubyObject rbNew(ThreadContext context, IRubyObject klazz, IRubyObject[] args) {
    Vec2 vec2 = (Vec2) ((RubyClass) klazz).allocate();
    vec2.init(context, args);
    return vec2;
}

/**
 *
 * @param runtime Ruby
 * @param klass RubyClass
 */
public Vec2(Ruby runtime, RubyClass klass) {
    super(runtime, klass);
}

void init(ThreadContext context, IRubyObject[] args) {
    int count = Arity.checkArgumentCount(context.runtime, args, Arity.OPTIONAL.getValue(), 2);
    if (count == 2) {
        jx = (args[0] instanceof RubyFloat) ? ((RubyFloat) args[0]).getValue() : ((RubyFixnum) args[0]).getDoubleValue();
        jy = (args[1] instanceof RubyFloat) ? ((RubyFloat) args[1]).getValue() : ((RubyFixnum) args[1]).getDoubleValue();
    }   // allow ruby ducktyping in constructor
    if (count == 1) {
        if (!(args[0].respondsTo("x"))) {throw context.runtime.newTypeError(args[0].getType() + " doesn't respond_to :x & :y");}
        jx = ((args[0].callMethod(context, "x")) instanceof RubyFloat)
                ? ((RubyFloat) args[0].callMethod(context, "x")).getValue() : ((RubyFixnum) args[0].callMethod(context, "x")).getDoubleValue();
        jy = ((args[0].callMethod(context, "y")) instanceof RubyFloat)
                ? ((RubyFloat) args[0].callMethod(context, "y")).getValue() : ((RubyFixnum) args[0].callMethod(context, "y")).getDoubleValue();
    }
}

Vec3D

/**
 *
 * @param context ThreadContext
 * @param klazz IRubyObject
 * @param args optional (no args jx = 0, jy = 0, jz = 0) (2 args jz = 0)
 * @return new Vec3 object (ruby)
 */
@JRubyMethod(name = "new", meta = true, rest = true)
public final static IRubyObject rbNew(ThreadContext context, IRubyObject klazz, IRubyObject[] args) {
    Vec3 vec = (Vec3) ((RubyClass) klazz).allocate();
    vec.init(context, args);
    return vec;
}

/**
 *
 * @param runtime Ruby
 * @param klass RubyClass
 */
public Vec3(Ruby runtime, RubyClass klass) {
    super(runtime, klass);
}

void init(ThreadContext context, IRubyObject[] args) {
    int count = Arity.checkArgumentCount(context.runtime, args, Arity.OPTIONAL.getValue(), 3);
    if (count >= 2) {
        jx = (args[0] instanceof RubyFloat)
                ? ((RubyFloat) args[0]).getValue() : ((RubyFixnum) args[0]).getDoubleValue();
        jy = (args[1] instanceof RubyFloat)
                ? ((RubyFloat) args[1]).getValue() : ((RubyFixnum) args[1]).getDoubleValue();
    }
    if (count == 3) {
        jz = (args[2] instanceof RubyFloat)
                ? ((RubyFloat) args[2]).getValue() : ((RubyFixnum) args[2]).getDoubleValue();
    } // allow ruby ducktyping in constructor
    if (count == 1) {
        if (!(args[0].respondsTo("x"))) {throw context.runtime.newTypeError(args[0].getType() + " doesn't respond_to :x & :y");}
        jx = ((args[0].callMethod(context, "x")) instanceof RubyFloat)
                ? ((RubyFloat) args[0].callMethod(context, "x")).getValue() : ((RubyFixnum) args[0].callMethod(context, "x")).getDoubleValue();
        jy = ((args[0].callMethod(context, "y")) instanceof RubyFloat)
                ? ((RubyFloat) args[0].callMethod(context, "y")).getValue() : ((RubyFixnum) args[0].callMethod(context, "y")).getDoubleValue();
        if (!(args[0].respondsTo("z"))) {return;} // allow promotion from 2D to 3D, sets jz = 0
        jz = ((args[0].callMethod(context, "z")) instanceof RubyFloat) ? ((RubyFloat) args[0].callMethod(context, "z")).getValue() : ((RubyFixnum) args[0].callMethod(context, "z")).getDoubleValue();
        }
    }

Usage

Simple example where we create a point instance that responds to :x and :y but typically could be any other class that provides such methods that return either a float or fixnum.

Point = Struct.new(:x, :y)
point = Point.new(10, 10)
vec = Vec2D.new(point)