由关键字触发的Scheme宏,不是列表的头部

Ord*_*Ord 7 lisp macros scheme

假设我想在除s-expression中第一项之外的其他内容上触发Scheme宏.例如,假设我想define用中缀样式替换:=,以便:

(a := 5) -> (define a 5)
((square x) := (* x x)) -> (define (square x) (* x x))
Run Code Online (Sandbox Code Playgroud)

实际的转变似乎非常简单.诀窍是让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))
Run Code Online (Sandbox Code Playgroud)

所以,我的问题是双重的:

  1. 如果我选择这with-infix-define条路线,我是否必须注意除引号和准引号之外的任何绊脚石?
  2. 我觉得有点像我正在重新发明轮子.这种类型的代码行走似乎正是标准宏扩展系统必须做的 - 唯一的区别是它们在决定是否进行任何代码转换时只查看列表中的第一项.有什么方法可以搭载现有的系统吗?

Eli*_*lay 12

  1. 在继续这一过程之前,最好彻底考虑一下 - IME你经常会发现你真正想要读者级别的处理:=作为中缀语法.这当然意味着它也会在引号等中加入,所以现在看起来很糟糕,但我的经验是,你最终意识到最好一贯地做事情.

  2. 为了完整起见,我会提到,在球拍有一个读语法黑客中缀般的表情:(x . define . 1)(define x 1).(和上面一样,它适用于所有地方.)

  3. 否则,您对包装宏的想法几乎是您唯一能做的事情.这并不会让它完全无望,但是你可能会在你的实现扩展器中有一个钩子,它可以让你做这样的事情 - 例如,Racket有一个特殊的宏叫做#%module-begin包装一个完整的模块体并#%top-interaction包装顶层表达式REPL.(这两个都是在两个上下文中隐式添加的.)这是一个例子(define-syntax-rule为了简单起见,我使用的是Racket ):

    #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)))
    
    Run Code Online (Sandbox Code Playgroud)

    如果我把它放在一个名为的文件中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)
    
    Run Code Online (Sandbox Code Playgroud)
  4. 是的,你需要处理一堆事情.上面的两个例子是处理begin表达式和处理函数体.这显然是一个很偏的列表-你还需要的机构lambda,let等等.但是,这仍然比一些盲人按摩更好,因为,当你真的不能告诉事先只是不实用怎么一些随机的代码段最终会结束.举个简单的例子,考虑一下这个简单的宏:

    (define-syntax-rule (track E)
      (begin (eprintf "Evaluating: ~s\n" 'E)
             E))
    (x := 1)
    
    Run Code Online (Sandbox Code Playgroud)
  5. 这样做的结果是,对于正确的解决方案,您需要一些方法来预先扩展代码,以便您可以扫描它并处理您的implmenetation中的几个已知核心形式.

  6. 是的,所有这些都是宏扩展器所做的重复工作,但是既然你正在改变扩展的工作方式,那么就无法解决这个问题.(要了解为什么它是一个根本性的变化,考虑类似的东西(if := 1)- 这是一个条件表达式还是一个定义?你如何决定哪一个优先?)因此,对于具有这种"可爱语法"的语言,一种更流行的方法是将代码读取并解析为普通的S表达式,然后让实际的语言实现使用普通的函数和宏.