【问题标题】:Scheme macro triggered by keyword which is not the head of a listScheme宏由不是列表头部的关键字触发
【发布时间】:2012-06-03 05:22:41
【问题描述】:

假设我想在 s 表达式中的第一项以外的其他内容上触发 Scheme 宏。例如,假设我想用中缀样式的:= 替换define,这样:

(a := 5) -> (define a 5)
((square x) := (* x x)) -> (define (square x) (* x x))

实际的转换似乎很简单。诀窍是让 Scheme 找到 := 表达式并对其进行宏扩展。我考虑过使用标准宏(可能是:(with-infix-define expr1 expr2 ...))围绕使用中缀语法的大部分代码,并让标准宏遍历其主体中的表达式并执行任何必要的转换。我知道如果我采用这种方法,我将不得不小心避免转换实际上应该是数据的列表,例如引用列表和准引用列表的某些部分。我设想的一个例子:

(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))

所以,我的问题有两个:

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

【问题讨论】:

  • Scheme 是否有 code-walker 包?似乎您需要做的就是将代码包装在 with-macro 中,遍历该代码并将 := 与之前的 s-exp 交换,然后定义一个使 := 等效于 define 的符号宏。但不确定引用的 s-exps。

标签: macros lisp scheme


【解决方案1】:
  1. 在继续此操作之前,最好仔细考虑一下——IME,您经常会发现您真正想要将:= 作为中缀语法的读者级处理。这当然意味着它也包含在引号等中,所以现在看起来很糟糕,但我的经验是,你最终会意识到最好始终如一地做事。

  2. 1234563 (如上所述,它适用于任何地方。)
  3. 否则,您对包装宏的想法几乎是您唯一能做的事情。但这并不意味着它完全没有希望,你可能有一个钩子到你的实现的扩展器中,可以让你做这样的事情——例如,Racket 有一个名为 #%module-begin 的特殊宏,它包装了一个完整的模块主体和 @987654322 @ 包装 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)
    
  4. 是的,你需要处理很多事情。上面的两个例子是处理begin 表达式和处理函数体。这显然是一个非常部分的列表——你还需要lambdalet 等的身体。但这仍然比一些盲目按摩要好,因为这不实用,因为你不能真正提前知道一些随机的代码将如何结束。作为一个简单的例子,考虑这个简单的宏:

    (define-syntax-rule (track E)
      (begin (eprintf "Evaluating: ~s\n" 'E)
             E))
    (x := 1)
    
  5. 这样做的结果是,为了获得正确的解决方案,您需要某种方法来预扩展代码,以便您可以扫描它并处理您的实现中为数不多的已知核心形式。

  6. 是的,所有这些都是重复宏扩展器所做的工作,但是由于您要更改扩展的工作方式,所以没有办法解决这个问题。 (要了解为什么这是一个根本性的变化,请考虑(if := 1) 之类的东西——这是一个条件表达式还是一个定义?你如何决定哪个优先?)因此,对于具有这种“可爱语法”的语言,一个更流行的方法是读取代码并将其解析为纯 S 表达式,然后让实际的语言实现使用纯函数和宏。

【讨论】:

  • 嗯...我可以看到我提出的解决方案很快就会变得非常丑陋...如果我决定走读者宏观路线,你知道我可以学习的任何好的资源吗关于 Scheme(尤其是 Racket)读取宏?我对它们的经验很少,但我似乎记得 Scheme 阅读器宏在我看来比 Common Lisp 阅读器宏功能要弱得多。特别是:我可以编写一个扩展为阅读器宏定义的常规宏吗?或者到那时修改阅读是否为时已晚?
  • Racket 有能力进行完全解析(这太多了),以及基于 CL 风格的基于 readtable 的阅读器宏(这也不太适合这里)。相反,最好像往常一样读取文件,然后找到看起来像您想要的 S-exprs,并通过将 := 向左移动来转换它们。但是可以使用#%module-begin 来做到这一点,只需创建一个执行相同操作的简单循环,然后将结果发送到通常的循环——但是这个循环应该使用真实代码完成,而不是使用syntax-rules
  • 好的,所以如果我正确理解了您的解决方案 - 我使用常规的 define-syntax 宏,非常像您的 my-module-begin 示例,然后我只需遍历传递给我的 s-exprs宏并根据需要将它们转换为前缀表达式?
  • 是的,但您无法使用 syntax-rules 宏来执行此操作。
【解决方案2】:

重新定义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 的预发布版本中可用)。

【讨论】:

  • 谢谢soegaard;但是为什么我不能将您的解决方案也应用于define 呢?如果每个表达式都隐含在 #%app 中,那不可行吗?
  • 简而言之:定义不是表达式。定义和表达可以出现在不同的地方。以 let 绑定的右侧为例。
  • 所以,不是每个define-like 表单都必须用#%app 包裹?还是某些不适合定义的表单用 #%app 包装(我假设,您的 let 示例)?
  • 不,只有应用程序包含在#%app 中。有关定义与表达式的法律上下文,请参阅 docs.racket-lang.org/reference/…。请参阅 pre.racket-lang.org/docs/html/reference/… 了解有关 #%app 如何显示的信息。
猜你喜欢
  • 2011-02-24
  • 2016-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-15
  • 2014-11-18
  • 2011-02-08
  • 2018-01-29
相关资源
最近更新 更多