我想创建一个包含其中一个功能的Racket宏
Clojure的线程宏,但另外需要一个回指的论点
(例如it
),允许我具体指出应将每个先前值插入下一个函数调用的位置。
我将从->>
宏(移植到scheme或racket)开始,其中每个新函数调用隐式地将前一个函数的值作为其最后一个参数插入:
(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"
我现在使用syntax-case定义一个名为>>
的宏:
当然,当it
不一定是这个时,我只需要这个新的宏
最后一个参数(当使用Clojure ->
时,也不总是第一个参数
宏)。
(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))
不幸的是,在评估时,宏返回45而不是15。
DrRacket的宏步进器显示扩展为:
(let ([it (range 10)])
(let ([it (map add1 it)])
(let ([it (take it 5)]) (apply + it))))
步进器还指示最终it
绑定到。{
(range 10)
返回的值。鉴于此信息,返回
值45解释。但为什么会这样呢?任何人都可以
帮我纠正这个词汇范围问题?
(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 ...)))]))
答案 0 :(得分:4)
这是一种方法。首先定义>>的版本使用显式的(在它下面称为>>>)。 >>的定义只需生成一个标识符并将其交给>>>。
#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))
输出结果为:
15
15
注意:我更喜欢你的语法参数化版本。
答案 1 :(得分:4)
更激进的方法是不要让>>
成为宏:
(define (>> it . fs)
((apply compose
(reverse fs))
it))
(>> 5 add1 even?) ;; => #t
现在它只是一个函数,它将赋予它的函数组合成一个函数,并调用与它的第一个参数it
组合的函数。这里的优点是你可以从组合函数的逻辑中以非常方式分离产生匿名函数的宏。例如,使用fancy-app包,它允许您使用_
作为参数的占位符来创建匿名函数:
(>> (range 10)
(map add1 _)
(take _ 5)
(apply + _))
这符合您的预期,>>
不再需要是一个宏,因此您可以将其用于生成其他功能的函数,并且它通常更灵活。您可以使用raco pkg install fancy-app
获取精美应用包,并在代码中使用(require fancy-app)
答案 2 :(得分:0)
我想说你的第一个宏与第二个版本完全不同,首先是因为它引入了共享。首先,这允许照射性结合被使用不止一次(或者可能根本不使用)。我会将共享版本>>=
称为更明确; - )
为什么呢?因为如果没有使用绑定,那么仍然应该为效果执行所有表达式上的所有表达式(这应该仍然是>>
的纯重写版本,我将其作为练习留下)< / p>
在任何情况下,您都不需要syntax-cases
的强大功能(假设您想要坚持使用宏解决方案)。以下是使用Petrofsky extraction和普通syntax-rules
编码照应线程宏的方法:
(define-syntax >>= (syntax-rules ()
([_ th fa] fa)
([_ th fa . fb]
(let-syntax ([K (syntax-rules () ([_ (this) ts]
(let ([this fa])
(>>= this . ts)))
)])
(extract* (th)
fb (K [] fb))
))
))
您的测试仍然有效:
(>>= _
(range 10)
(map add1 _)
(take _ 5)
(apply + _)) ;; => 15
P.S。提示:对于获取>>
(>>=
的重写版本),只需更改>>=
定义中的一个表单; - )
P.P.S。以下是syntax-rules
extract*
,礼貌Al Petrofsky&amp; Oleg Kiselyov
(define-syntax-rule [extract symb body _k]
(letrec-syntax ([tr (syntax-rules (symb)
([_ x symb tail (k symb-l . args)]
(k (x . symb-l) . args))
([_ d (x . y) tail k]
(tr x x (y . tail) k))
([_ d1 d2 () (k symb-l . args)]
(k (symb . symb-l) . args))
([_ d1 d2 (x . y) k]
(tr x x y k)))])
(tr body body () _k))
)
(define-syntax extract* (syntax-rules ()
([_ (symb) body k]
(extract symb body k))
([_ _symbs _body _k]
(letrec-syntax ([ex-aux
(syntax-rules ()
([_ found-symbs () body k]
(reverse () found-symbs k))
([_ found-symbs (symb . symb-others) body k]
(extract symb body
(ex-aux found-symbs symb-others body k)))
)]
(reverse
(syntax-rules ()
([_ res () (k' () . args)]
(k' res . args))
([_ res (x . tail) k]
(reverse (x . res) tail k)))))
(ex-aux () _symbs _body _k)))))