Wednesday, June 13, 2007

Hacking JRuby: More on Method Arguments

I closed my last post with this observation:
"...JRuby does not have an equivalent for rb_scan_args(), or at least not one that is called on a per-method basis."

In the comments, Ola Bini -- ThoughtWorker and JRuby committer -- graciously pointed out that there are, in fact, two static methods that we can use to simulate the function of rb_scan_args(), both found in the org.jruby.runtime.Arity class: checkArgumentCount(), and scanArgs().

checkArgumentCount() does just what it says. You pass it an array of argument values, along with the minimum and maximum number of all arguments, and it verifies that the actual number of arguments falls within that range (inclusive). So this provides some basic sanity checking of the number of arguments passed to the method you're implementing. If the number of arguments is valid, checkArgumentCount() returns the actual number of arguments passed. I guess you could argue that this isn't very useful, since you already know the number of arguments you expect, as well as the number given to you. But it is used in a number of places in the JRuby source code.

scanArgs() will do a little more work for you. It will do the same sanity checking (it actually calls checkArgumentCount() to do so), although you specify the numbers slightly differently, passing the number of required arguments along with the number of optional arguments. If the actual number of arguments passed is valid, scanArgs() then creates a new array of length required + optional, copies the values of any arguments passed in (all required arguments plus any provided optional arguments), sets the values of any non-provided optional arguments to nil, and returns the new array. This actually is useful, as it transforms a variable-length array of arguments into one of fixed length, where the fixed length is always equal to the maximum number of arguments your method will accept.

As Ola points out, this still is not quite the equivalent of MRI rb_scan_args(), in that it doesn't handle "rest" or "block" arguments. But it does offload from us some of the burden of handling variable length argument lists.

I do plan to use scanArgs() to finish up my implementation of BigDecimal.mode(), but in order to so meaningfully I'm also going to have to change mode()'s method definition from taking a fixed number of arguments to taking a variable number of arguments (the first argument is required, the second is optional). That's going to involve digging into JRuby's mechanism for defining Ruby methods, which I've researched and found to be pretty cool, but it's going to take another few blog posts to sort out. Stay tuned...