[英]how to create a macro in racket where a list becomes the args of said lambda?
[英]Making a racket macro that generates a lambda
我正在嘗試制作一個小shell,用於對csv文件進行類似sql的查詢(出於好奇和嘗試學習Racket)。 為此,我想實現一個具有這種粗略結構的select宏(我計划將x作為db的列,但現在只傳遞一行):
(define-syntax select
(syntax-rules (* from where)
((_ col1 ... from db where condition)
(filter (lambda (x) condition) <minutiae>))))
(其中細節是文件IO和管道代碼)
x的范圍不是我想象的那樣:
x: undefined;
cannot reference an identifier before its definition
我找到了這個類似於let的宏的例子 :
(define-syntax my-let*
(syntax-rules ()
((_ ((binding expression) ...) body ...)
(let ()
(define binding expression) ...
body ...))))
然后我開始嘗試像這樣生成lambda:
(define-syntax my-lambda
(syntax-rules ()
((_ body)
(lambda (x)
body))))
然后嘗試模仿let示例的結構:
(define-syntax my-lambda
(syntax-rules ()
((_ body)
(lambda (x_)
(let ()
(define x x_)
body)))))
這兩個都在調用時給了我同樣的錯誤((my-lambda (+ x 1)) 0)
:
x: undefined;
cannot reference an identifier before its definition
根據我的閱讀,這是由於衛生,但我似乎無法把握得足以解決這個問題。 我做錯了什么以及如何定義這些類型的宏? 為什么let示例工作但不是lambda?
就像你猜測的那樣,問題在於衛生問題。
let示例有效,因為給定正文中使用的標識符被傳遞給宏。
但是如果你試圖在身體內部定義一個x
標識符,而沒有讓身體的人真正明確地了解它,那么你就會破壞衛生(在范圍內插入一個任意的綁定)。
你想創建被稱為照應宏。 幸運的是,Racket有你需要的東西。
如果您曾經使用過Racket參數,它的工作方式有點相同,但對於宏。
(define-syntax-parameter <x>
(lambda (stx)
(raise-syntax-error '<x> "Used outside select macro." stx)))
這將定義一個名為<x>
的參數,您的宏用戶將能夠在您的select
宏中使用。 為防止在外部使用它,默認情況下,該參數配置為引發語法錯誤。
要定義可以使用它的唯一位置,可以調用syntax-parameterize
:
(define-syntax select
(syntax-rules (* from where)
[(_ col1 ... from db where condition)
(findf
(lambda (x)
(syntax-parameterize ([<x> (make-rename-transformer #'x)])
condition))
<minutiae>)]))
這將創建圍繞一個新的范圍condition
,其中<x>
結合到x
在拉姆達。
然后你可以這樣調用你的宏:
(select * from db where (eq? <x> 'foo))
如果您嘗試在宏之外使用<x>
,則會出現語法錯誤:
> (displayln <x>)
<x>: Used outside select macro.
in: <x>
#lang racket/base
(require
(for-syntax racket/base)
racket/stxparam)
(define-syntax-parameter <x>
(lambda (stx)
(raise-syntax-error '<x> "Used outside select macro." stx)))
(define-syntax select
(syntax-rules (* from where)
[(_ col1 ... from db where condition)
(findf
(lambda (x)
(syntax-parameterize ([<x> (make-rename-transformer #'x)])
condition))
db)]))
(module+ test
(require rackunit)
(define db '(foo bar baz))
(check-equal? (select * from db where (eq? <x> 'foo)) 'foo)
(check-equal? (select * from db where (eq? <x> 'bar)) 'bar)
(check-equal? (select * from db where (eq? <x> 'boop)) #f))
這有效:
(define-syntax my-lambda
(syntax-rules ()
((_ x body)
(lambda (x) body))))
(my-lambda x (+ x 1))
雖然這不起作用:
(define-syntax my-lambda*
(syntax-rules ()
((_ body)
(lambda (x) body))))
(my-lambda* (+ x 1))
可能有助於理解差異的一件事是,只要重命名是一致的,Racket就可以重命名變量(實際上很難在這里定義單詞一致,但直覺上它應該對你有意義)。 球拍需要能夠重命名以保持衛生。
在第一種情況下,你調用(my-lambda x (+ x 1))
。 Racket可能會將其重命名為(my-lambda x$0 (+ x$0 1))
。 擴展宏,我們得到(lambda (x$0) (+ x$0 1))
這是有效的。
在第二種情況下,你調用(my-lambda* (+ x 1))
。 Racket可能會將其重命名為(my-lambda* (+ x$0 1))
。 擴展宏,我們得到(lambda (x) (+ x$0 1))
,因此x$0
是未綁定的。
(請注意,我正在簡化很多事情。實際上,在第一種情況下,Racket需要擴展宏,以便知道它需要在同一時間重命名兩個x
。)
為了得到你想要的東西,你需要打破衛生。 這是一個簡單的方法:
(require syntax/parse/define)
(define-simple-macro (my-lambda** body)
#:with unhygiene-x (datum->syntax this-syntax 'x)
(lambda (unhygiene-x) body))
(my-lambda** (+ x 1))
另請參閱https://stackoverflow.com/a/55899542/718349以及下面的評論,以獲得打破衛生的替代方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.