I've been transcoding a lot of Haskell to Scheme lately and one of the things that I found myself needing was a macro for dealing with Currying of functions in a way that handles partial and over-application cleanly.
I found a very elegant macro by Piet Delport that handles partial application, but that doesn't deal with the partial application of no arguments or that I'd like to also be able to say things like the following and have the extra arguments be passed along to the result.
(define-curried (id x) x)
(id + 1 2 3) ;;=> 6
This of course, becomes more useful for more complicated definitions.
(define-curried (compose f g x) (f (g x)))
(define-curried (const a _) a)
While I could manually associate the parentheses to the left and nest lambdas everywhere, Haskell code is rife with these sorts of applications. In the spirit of Scheme, since I couldn't find a macro on the internet that did what I want, I tried my hand at rolling my own.
;; curried lambda
(define-syntax curried
(syntax-rules ()
((_ () body)
(lambda args
(if (null? args)
body
(apply body args))))
((_ (arg) body)
(letrec
((partial-application
(lambda args
(if (null? args)
partial-application
(let ((arg (car args))
(rest (cdr args)))
(if (null? rest)
body
(apply body rest)))))))
partial-application))
((_ (arg args ...) body)
(letrec
((partial-application
(lambda all-args
(if (null? all-args)
partial-application
(let ((arg (car all-args))
(rest (cdr all-args)))
(let ((next (curried (args ...) body)))
(if (null? rest)
next
(apply next rest))))))))
partial-application))))
;; curried defines
(define-syntax define-curried
(syntax-rules ()
((define-curried (name args ...) body)
(define name (curried (args ...) body)))
((define-curried (name) body)
(define name (curried () body)))
((define-curried name body)
(define name body))))
While Scheme is not my usual programming language, I love the power of hygienic macros.
I welcome feedback.
[Edit: updated to change the base case for define-curried and added the if to the base case of curried to be more consistent with the other cases per the second comment below]
Discussion
@Anthony:
Good point. I was trying to avoid discriminating, so I could just work parametrically, but a quick
procedure?check isn’t such a high price to pay to handle the base case more gracefully.I have two concerns with this proposed fix though.
null?check onargsrather than swallow them in the non-procedure case.By redefining
(define-curried name body)to just use adefineand migrating its current behavior to(define-curried (name) body)this obtains, perhaps a more pleasing result. Patching it thus:((_ () body) (lambda args (if (null? args) body (apply body args))))yields a result that is identical to the handling of the other two cases in behavior.
Then
(define-curried x 5)does the right thing, and just gives you 5.and
(define-curried (x) 5)gives you a nullary function that when invoked will give you back 5, but will try to pass any superfluous over-applied arguments to 5 and crash.@Phil:
Nice macro. =) While, alas, it doesn’t handle over or null application, it is really succinct!
Edward,
I like your fix; a definite improvement! Can you offer more detail on your parametricity concern, though? Like many others, I, too, have a toy monad library in my collection, but it’s not obvious to me how this situation with my proposed patch raises a problem in that area.
@Anthony:
It looks like my parametricity concern was a bit of a misapprehension. I wasn’t able to concoct a scenario where it led to a problem. ;)
If we ignore the existence of nullary functions the new version works really well, as would, I suppose, your fix.
Hi Dan,
The code above provides both.
(define id (curried (x) x))uses the lambda form (perhaps ‘
curried’ should be renamed ‘curried-lambda’ ?)On the other hand,
(define-curried (id x) x)provides a definition with a
curriedargument list in the same fashion that(define (id x) x)desugars to(define id (lambda (x) x))— a convenience that I seem to recall you don’t like using. ;)