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)
= (arr, op ptr) ptrMotion op arr ptr
We also lose the NAME
argument, since we can just use
those names directly.
= ptrMotion (+1)
gt = ptrMotion id
eq = ptrMotion (-1) lt
You might need something like this for a functional bf implementation↩︎