mitchell vitez

dark mode

blog about music art media

resume email github

Currying and Macros

Consider the following example Racket code:1

(define (gt arr ptr) (list arr (add1 ptr)))
(define (eq arr ptr) (list arr ptr))
(define (lt arr ptr) (list arr (sub1 ptr)))

This is getting a bit repetitive, so our instinct as lisp programmers might be to write a macro that captures the repetition. The only things that change across definitions are the function name, and the thing that it does to ptr, which we can call NAME and OP respectively.

(define-macro (ptr-motion NAME OP)
  #'(define (NAME arr ptr) (list arr (OP ptr))))

With this macro, now we can define our above operations in terms of their common functionality, which makes this much simpler code. Also, if we ever want to extend this code (as I did to include eq) it’s better to trust the macro than attempt to rewrite possibly-complicated functionality after not looking at it for a while.

(ptr-motion gt add1)
(ptr-motion eq id)
(ptr-motion lt sub1)

This is actually a case where code-writing macros are a bit overkill, though. All we really need are higher-order functions (so we can pass around OP), and some way to easily create functions from other functions, but with some arguments applied. Because all functions are curried in Haskell, it becomes extremely easy to write this macro as a plain function, and then create the operations we want by partial application to that function.

ptrMotion :: (Int -> Int) -> Vector -> Int -> (Vector, Int)
ptrMotion op arr ptr = (arr, op ptr)

We also lose the NAME argument, since we can just use those names directly.

gt = ptrMotion (+1)
eq = ptrMotion id
lt = ptrMotion (-1)