I work in simulation and often need multiple data generators which are structurally identical but have different parameterizations. I'm trying to write a gem using C extensions to implement a factory method, which determines and creates an appropriate type of enumerator based on the parameterization provided. The following is not my actual code, but is intended as a minimal reproducible example to illustrate the behavior I find confusing:
#include "ruby.h"
VALUE rb_mTest = Qnil;
VALUE rb_cTest = Qnil;
static VALUE super_initialize(VALUE self) {
return self;
}
static VALUE rand_enum(VALUE self) {
RETURN_SIZED_ENUMERATOR(self, 0, 0, 0);
VALUE ary[1] = {LL2NUM(42)};
for(;;) {
rb_yield(rb_funcallv(rb_mKernel, rb_intern("rand"), 1, ary));
}
return Qnil;
}
static VALUE enum_by_2(int32_t argc, VALUE* argv , VALUE self) {
rb_check_arity(argc, 1, 1);
int64_t value = NUM2LL(argv[0]);
RETURN_SIZED_ENUMERATOR(self, 1, argv, (1ll << 63) - value);
for(;;) {
rb_yield(LL2NUM(value));
value += 2;
}
return Qnil;
}
static VALUE enum_factory(int32_t argc, VALUE* argv, VALUE self) {
VALUE argument;
rb_scan_args(argc, argv, "01", &argument);
switch (TYPE(argument)) {
case T_NIL:
printf("It's a nil\n");
return rand_enum(self);
case T_FIXNUM:
printf("It's a FIXNUM\n");
return enum_by_2(1, argv, self);
default:
printf("Unrecognized argument type\n");
return Qnil;
}
}
void Init_test(void) {
rb_mTest = rb_define_module("Test");
rb_cTest = rb_define_class_under(rb_mTest, "Tester", rb_cObject);
rb_define_method(rb_cTest, "initialize", super_initialize, 0);
rb_define_method(rb_cTest, "enum_factory", enum_factory, -1);
}
This code works, but in a way that I didn't expect. Here's an irb session from a test run after compiling the code above:
irb(main):001:0> require_relative 'lib/test'
=> true
irb(main):002:0> t = Test::Tester.new
=> #<Test::Tester:0x0000000104b23c70>
irb(main):003:0> g1 = t.enum_factory
It's a nil
=> #<Enumerator: ...>
irb(main):004:0> g2 = t.enum_factory(10)
It's a FIXNUM
=> #<Enumerator: ...>
irb(main):005:0> g1.take(5).to_a
It's a nil
=> [35, 21, 30, 20, 0]
irb(main):006:0> g2.take(5).to_a
It's a FIXNUM
=> [10, 12, 14, 16, 18]
As you can see, the enum_factory switches to return different enumerators based on the parameterization, and the printf statements and results indicate that the correct parameterization is being applied. My confusion stems from the printf output which shows that subsequent method calls applied directly to g1 and g2 still seem to be going through the factory method. My work often generates samples into the millions or even tens of millions, so my intentions were to maximize speed by writing this in C and to avoid parsing the parameters after doing it once in the factory, but clearly that's not what's happening. I'm probably misreading the (sparse) C API documentation and missing something trivial or obvious. I'd appreciate any pointers to where my misunderstanding lies.