Something Like PyArg_ParseTuple For Ruby

November 17th 23:58
by why

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 …

8 comments

automatthew

said on November 17th 18:24

Aha! I knew you were a “conservative|liberal”.

OMouse

said on November 17th 22:04

doesn’t Ruby have symbols? or am I thinking of Smalltalk/Lisp?

Dr Nic

said on November 18th 05:58

Beware the snakes!

j`ey

said on November 18th 07:26

beware the everything :S

automatthew

said on November 18th 09:30

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

said on November 19th 14:40

“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

said on November 19th 14:46

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

said on November 20th 21:41

very neat. fits well with ruby’s “all about the expressiveness” mindset.

Comments are closed for this entry.