假设我想在s-expression中第一项之外的其他内容上触发Scheme宏。例如,假设我想将define
替换为中缀式:=
,以便:
(a := 5) -> (define a 5)
((square x) := (* x x)) -> (define (square x) (* x x))
实际的转变似乎非常简单。诀窍是让Scheme找到:=
表达式并对它们进行宏扩展。我已经考虑过围绕使用中缀语法和标准宏的大部分代码,可能是:(with-infix-define expr1 expr2 ...)
,并让标准宏遍历其正文中的表达式并执行任何必要的转换。我知道如果我采用这种方法,我必须小心避免转换实际上应该是数据的列表,例如引用列表和quasiquoted列表的某些部分。我想象的一个例子:
(with-infix-define
((make-adder n) := (lambda (m) (+ n m)))
((foo) :=
(add-3 := (make-adder 3))
(add-6 := (make-adder 6))
(let ((a 5) (b 6))
(+ (add-3 a) (add-6 b))))
(display (foo))
(display '(This := should not be transformed))
所以,我的问题是双重的:
with-infix-define
路线,我是否必须注意除引号和准引号之外的任何绊脚石?答案 0 :(得分:12)
在继续这一过程之前,最好彻底思考问题 - IME你经常会发现你真正希望读者级别处理:=
作为中缀语法。这当然意味着它也会在引用中加上等等,所以现在看起来很糟糕,但同样,我的经验是你最终意识到一直做事情会更好。
为了完整起见,我会在Racket中提到有类似中缀表达式的读取语法攻击:(x . define . 1)
读取为(define x 1)
。 (如上所述,它适用于所有地方。)
否则,您对包装宏的想法几乎是您唯一能做的事情。但是,这并没有让它完全没有希望,你可能有一个钩子进入你的实现的扩展器,可以让你做这样的事情 - 例如,Racket有一个特殊的宏,称为#%module-begin
,它包含一个完整的模块体和#%top-interaction
包装REPL上的顶层表达式。 (这两个都是在两个上下文中隐式添加的。)这是一个例子(为简单起见,我使用的是Racket的define-syntax-rule
):
#lang racket/base
(provide (except-out (all-from-out racket/base)
#%module-begin #%top-interaction)
(rename-out [my-module-begin #%module-begin]
[my-top-interaction #%top-interaction]))
(define-syntax infix-def
(syntax-rules (:= begin)
[(_ (begin E ...)) (begin (infix-def E) ...)]
[(_ (x := E ...)) (define x (infix-def E) ...)]
[(_ E) E]))
(define-syntax-rule (my-module-begin E ...)
(#%module-begin (infix-def E) ...))
(define-syntax-rule (my-top-interaction . E)
(#%top-interaction . (infix-def E)))
如果我把它放在一个名为my-lang.rkt
的文件中,我现在可以按如下方式使用它:
#lang s-exp "my-lang.rkt"
(x := 10)
((fib n) :=
(done? := (<= n 1))
(if done? n (+ (fib (- n 1)) (fib (- n 2)))))
(fib x)
是的,你需要处理一堆事情。上面的两个例子是处理begin
表达式和处理函数体。这显然是一个非常局部的清单 - 你也需要lambda
,let
等身体。但这仍然比一些盲目的按摩更好,因为那是不实际的,因为你不能真的告诉我们一些随机代码会如何结束。举个简单的例子,考虑一下这个简单的宏:
(define-syntax-rule (track E)
(begin (eprintf "Evaluating: ~s\n" 'E)
E))
(x := 1)
这样做的结果是,对于正确的解决方案,您需要一些方法来预扩展代码,以便您可以扫描它并处理您的implmenetation中的几个已知核心形式。
是的,所有这些都是宏扩展器所做的重复工作,但由于你正在改变扩展的工作原理,所以没有办法解决这个问题。 (要了解为什么它是一个根本性的变化,考虑像(if := 1)
这样的东西 - 这是一个条件表达式还是一个定义?你如何决定哪个优先??因此,对于具有这种“可爱语法”的语言一种比较流行的方法是将代码读取并解析为普通的S表达式,然后让实际的语言实现使用普通的函数和宏。
答案 1 :(得分:3)
重新定义define
有点复杂。请参阅@ Eli的优秀解释。
另一方面,如果您满意:=
使用set!
,事情就会变得更简单。
这是一个小例子:
#lang racket
(module assignment racket
(provide (rename-out [app #%app]))
(define-syntax (app stx)
(syntax-case stx (:=)
[(_ id := expr)
(identifier? #'id)
(syntax/loc stx (set! id expr))]
[(_ . more)
(syntax/loc stx (#%app . more))])))
(require 'assignment)
(define x 41)
(x := (+ x 1))
(displayln x)
为了将示例保留为单个文件,我使用了子模块(在Racket的预发布版本中可用)。