Passing on Keyword Arguments in PLT Scheme

I am a big fan of Python’s keyword argument facility, and especially its support for *args and **kwargs function parameter declarations. *args and **kwargs capture any explicitly undeclared positional and keyword arguments, respectively, and this facility in many cases allows one to avoid repeating function interfaces. This is both less typing and more future proof, and makes it easier to see the parameters that directly concern a function. Contrast this with Java, which not only has no keyword arguments, and no support for *args style declarations, but which also forces you to repeat caught exception declarations in function signatures.

In PLT Scheme version 3, there was no support for keyword arguments built into the language as such, but there was a handy kw.ss module for adding support for keyword arguments. Thanks to the language extensibility enabled by macros, the kw.ss-provided keyword argument support was (and is) a pleasure to use, and I’ve accumulated a lot of code making use of kw.ss.

When using kw.ss, keyword arguments are passed like any other arguments, but it is possible to declare “special” functions that actually interpret the passed keyword arguments. A common idiom I would use is shown by the following code:

(define (create-file filename data . rest)
  (verbose-puts (format "write file ~a" filename))
  (unless (simulate)
    (apply
     call-with-output-file
     filename
     (lambda (output)
       (display data output))
     rest)))

Here, the create-file function only cares about the first two positional arguments filename and data, and passes anything else—whether positional or keyword arguments—to call-with-output-file. So the rest bit serves both as *args and **kwargs, and we need not know whether call-with-output-file takes its options as positional or keyword arguments, or both. In fact in PLT Scheme v3 it takes them as positional arguments, while in the recently released PLT Scheme version 4 it takes them as keyword arguments. One might hope that the above code (which I wrote for v3) would be compatible both with v3 and v4, but unfortunately this is not quite the case.

PLT Scheme v4 has a kw.ss-incompatible keyword argument implementation built into the language, and while PLT Scheme has admirable support for backward compatibility, for many modules it probably makes sense to switch to the v4 scheme language eventually, which then would involve tweaking any keyword argument passing. Somewhat surpringly (to me), even the above code is not compatible with the scheme keyword arguments, even though it does not explicitly mention such arguments.

If a function does not look like it accepts keyword arguments, and yet it is invoked with keyword arguments, the compiler will complain. For instance, in the following code the invocation of foo with a keyword argument is an error, while invoking it without arguments would be acceptable.

(define (bar #:a (a 1))
  (write-nl a))

(define (foo . op)
  (apply bar op))

(foo #:a 555)

Now, v4 presently has no easy way to declare **kwargs, and the question then is how to implement the pass-on-keyword-arguments idiom in v4 without too much typing. Luckily, it seems that macros can offer pain relief in almost any situation where the language given to you is in some way tedious to use. My initial solution to passing along keyword arguments, and a v4 version of create-file is given below. Naturally, if desired, it ought to also be possible to implement a define/pass-kw macro, and a kw.ss compatible version of lambda/pass-kw.

(define-syntax lambda/pass-kw
  (syntax-rules ()
    ((_ arg-spec apply/kw body ...)
     (make-keyword-procedure
      (lambda (kw-names kw-vals . args)
        (let ((apply/kw
               (lambda (f . rest)
                 (keyword-apply f kw-names kw-vals rest))))
          (apply
           (lambda arg-spec body ...)
           args)))))))

(define create-file
  (lambda/pass-kw (filename data) apply/kw
    (verbose-puts (format "write file ~a" filename))
    (unless (simulate)
      (apply/kw
       call-with-output-file
       filename
       (lambda (output)
         (display data output))))))