Something Like PyArg_ParseTuple For Ruby
Calling into Ruby from C is great, but I’ve noticed that I spend a lot of time casting arguments coming into each function.
rb_scan_args(argc, argv, "11", &port, &opts);
if (rb_respond_to(port, rb_intern("to_str"))
StringValue(port);
else if (!rb_respond_to(port, rb_intern("read")))
rb_raise(rb_eArgError, "a String or IO object only, please");
if (TYPE(opts) != T_HASH && !NIL_P(opts))
rb_raise(rb_eArgError, "options must be a hash");
Ruby is dynamically typed, but object types are a bit more reified in C. The TYPE macro can check an object to see if it’s a T_FIXNUM, T_HASH, T_STRING, T_ICLASS, etc. You can duck type all you want, but when you’re inside an extension, you’ll need to know the type before calling rb_str_cat
or rb_hash_aref
.
And StringValue
does this, it’ll cast using to_str
and then make sure you’ve actually a real T_STRING.
I’m disappointed with rb_scan_args
. It’s wimpy. The function signatures it uses aren’t very expressive. It’s basically describing arity and that’s it.
One thing I like better in Python’s API, though, is the PyArg_ParseTuple function and its cousins.
const char *file;
const char *mode = "r";
int bufsize = 0;
ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
/* A string, and optionally another string and an integer */
/* Possible Python calls:
f('spam')
f('spam', 'w')
f('spam', 'wb', 100000) */
The function signature is more expressive here ("s|si"
), indicating which types are allowed. You don’t have to check the types individually, nor do you need to throw individual exceptions.
Here’s an equivalent I’m working on for Ruby:
rb_arg_list args;
rb_parse_args(argc, argv, "s|h,-|h", &args);
/* a string and optionally a hash OR an IO and an optional hash */
The rb_parse_args
function returns the number of the match. If the first signature ("s|h"
) is matched, you get 1. If the second signature is matched, you get 2.
I’m planting this in a switch statement when I want to overload a method.
rb_arg_list args;
/* "k" means Class, "h" means Hash */
switch (rb_parse_args(argc, argv, "kh,h,", &args))
{
case 1: /* "kh" - style(Link, :background => white) */ break;
case 2: /* "h" - style(:width => "100%") */ break;
case 3: /* "" - style() => {...hash of styles...} */ break;
}
This is saving me some tedium. In typical shoddy form, my error messages blow. I am undisciplined’s middle name.
Ah, how good it feels to be inspired by Python. A bit good, a bit slimey. I’m like Joe Lieberman, guys.
Now begin the comments …
automatthew
Aha! I knew you were a “conservative|liberal”.
OMouse
doesn’t Ruby have symbols? or am I thinking of Smalltalk/Lisp?
Dr Nic
Beware the snakes!
j`ey
beware the everything :S
automatthew
OMouse: Ruby does have symbols, signified by a colon-preceded identifier:
:symbol1, :other_symbol
All the (non-Python) code in this post appears to be from a C extension to Ruby.
Daniel Berger
“I’m disappointed with rb_scan_args. It’s wimpy. The function signatures it uses aren’t very expressive. It’s basically describing arity and that’s it.”
Then I guess you’re disappointed with plain Ruby, since it doesn’t check argument types, either.
Still, it would be handy to have something like this.
Rubypanther
Daniel,
It may be that it’s not so much that it’s cared about, but that in C you Have To Know. So it’s disappointing when it’s a PITA .
zem
very neat. fits well with ruby’s “all about the expressiveness” mindset.
Comments are closed for this entry.