簡體   English   中英

制作一個生成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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM