I want to create a Racket macro which includes the functionality of one of
Clojure's threading macros, but in addition requires an anaphoric argument
(for example it
), permitting me to specifically indicate where each previous value should be inserted into the next function call.
I’ll start with the ->>
macro (ported to scheme or racket) where each new function call implicitly inserts the value of the previous function as its last argument:
(define-syntax ->>
(syntax-rules ()
[(_ x) x]
[(_ x (y ...) rest ...)
(->> (y ... x) rest ...)]))
(->> (range 10)
(map add1)
(apply +)
(printf "the sum is: ~a\n"))
; "the sum is: 55"
I now define a macro named >>
using syntax-case:
Of course, I'll only need this new macro when it
is not necessarily the
last argument (nor always the first argument when using Clojure's ->
macro).
(define-syntax (>> stx)
(syntax-case stx ()
[(_ x) #'x]
[(_ x (y ...) rest ...)
(with-syntax ([it (datum->syntax stx 'it)])
#'(let ([it x])
(>> (y ...) rest ...)))]))
(>> (range 10)
(map add1 it)
(take it 5) ; 'it' is not the last argument
(apply + it))
Unfortunately, when evaluated the macro returns 45 rather than 15.
DrRacket's macro stepper shows the expansion to be:
(let ([it (range 10)])
(let ([it (map add1 it)])
(let ([it (take it 5)]) (apply + it))))
The stepper also indicates that the final it
is bound to the
value returned by (range 10)
. Given this information, the return
value of 45 is explained. But why is this the case? Can anyone
help me to correct this lexical scoping problem?
By the way, the macro written slightly differently with syntax-parameterize works fine:
(require racket/stxparam)
(define-syntax-parameter it
(lambda (stx)
(raise-syntax-error #f "can only be used inside '>>'" stx)))
(define-syntax >>
(syntax-rules ()
[(_ x) x]
[(_ x (y ...) rest ...)
(let ([val x])
(syntax-parameterize ([it (make-rename-transformer #'val)])
(>> (y ...) rest ...)))]))
Here is one way to do it. First define a version of >> that uses an explicit it (below it is called >>>). The definition of >> simply generates an identifier and hands it to >>>.
#lang racket
(define-syntax (>>> stx)
(syntax-case stx ()
[(_ it x) #'x]
[(_ it x (y ...) rest ...)
#'(let ([it x])
(>>> it (y ...) rest ...))]))
(>>> it
(range 10)
(map add1 it)
(take it 5)
(apply + it))
(define-syntax (>> stx)
(with-syntax ([it (datum->syntax stx 'it)])
(syntax-case stx ()
[(_ . more)
#'(>>> it . more)])))
(>> (range 10)
(map add1 it)
(take it 5)
(apply + it))
The output is:
15
15
Note: I prefer your syntax-parameterize version.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With