方案:全局宏(define-syntax)内的局部宏(let-syntax)

问题描述 投票:0回答:1

我正在尝试编写一组Scheme宏来简化使用声明式风格构建简单的图形结构,但作为一个初学者Schemer,我正在努力解决宏卫生问题。

我定义了两种记录类型

:graph
:node
:

(define-record-type :graph 
  (make-graph nodes links) 
  graph? 
  (nodes graph-nodes) 
  (links node-links))

(define-record-type :node
  (make-node name pins)
  node?
  (name node-name)
  (pins node-pins))
在我的示例中,

pin
只是一个字符串(引脚名称),
link
只是
endpoint
的缺点对,而
endpoint
则是节点名称和引脚名称的缺点对。

我希望能够做的是定义宏的

graph
node
link
,允许我以声明式风格创建图形,如下所示:

(define g (graph
  (node "v0"
    (pin "out"))

  (node "v1"
    (pin "out"))

  (node "add"
    (pin "lhs")
    (pin "rhs")
    (pin "out"))

  (link (("v0" . "out") . ("add" . "lhs"))
  (link (("v1" . "out") . ("add" . "rhs"))))

此表达式的结果将是

:graph
类型的值,其中
(graph-nodes g)
将计算为 3 个
:node
值的列表,
(graph-links g)
将计算为
((("v0"."out") . "add" . "lhs")) (("v1" . "out") . ("add" . "rhs")))
,并且 3 个节点中的每一个都将是
:node
值与定义它们的
pins

一个额外的要求是,只有

graph
宏才能在全局范围内定义,我非常希望避免为我可以作为
body
调用的
graph
传递的任何内容定义全局宏。这些宏仅在
graph
宏扩展的上下文中才有意义,我想为它们维护“尽可能小的范围”原则,并且也不用像
node
这样的通用名称来乱扔全局范围或
pin

显然,直接使用

make-graph
创建图形并将节点和链接直接作为列表传递会相对简单。不过,我更喜欢使用宏,因为它允许使用更具声明性的样式来定义图形:您不需要记住要传入的参数的顺序,您可以混合节点/链接声明的顺序,扩展图形在不破坏图形定义代码的情况下记录更多附加字段,轻松地将宏调用转换为其他数据类型(XML、JSN)等。

我尝试使用嵌套宏来解决这个问题:全局

graph
宏,它有一个本地
node
宏。

为了简单起见,我将在此示例中省略定义链接或引脚,以扩展此示例,

let-syntax
将有一个附加宏
link
,并且
node
的宏本身将有另一个名为的嵌套宏
pin
(所以
let-syntax
代表
pin
内部
let-syntax
代表
node
内部
define-syntax
代表
graph
):

(define-syntax graph
  (syntax-rules ()
    ((graph . body)

      (let ((nodes '())
            (links '()))
        
        (let-syntax ((node
          (syntax-rules ()
            ((node name))
            (make-node name '()))))
         
        (begin . body))

      (make-graph nodes links)))))

(graph (node "v0") (node "v1"))

不幸的是,这不起作用,因为在节点扩展中插入

node
时,
body
将被解除绑定。我想我明白为什么:由于Scheme宏是卫生的,任何变量(模式变量或局部变量)都将被扩展/重命名为宏局部变量,因此由
node
定义的
let-syntax
宏实际上不会被称为
node
扩展后不再存在,因此我传递到
node
宏中的
body
中的
graph
将看不到它。

这里提出了一个非常相似的问题,虽然我有点理解那里的解释,但我不确定如何解释建议的解决方案/解决方法。

如果我没读错的话,另一个问题中的两个解决方案都涉及a)显式传递/匹配对我想从传递到

body

中调用的任何函数的引用(所以在我的例子中,
graph
node
link
),或 b) 将 
pin
node
link
 定义为全局宏。
解决方案 a) 没有吸引力,因为它违背了宏的目的(即以声明式风格定义图形,使用最少的语法),而解决方案 b) 感觉它违背了卫生宏的全部意义,因为现在我需要在全局范围内使用像 
pin

node

link
 这样的宏,它们很可能与想要定义同名事物(局部变量、符号)的代码发生冲突。
考虑到人们已经能够使用Scheme宏创建DSL来完成最疯狂的事情,我几乎可以肯定我想做的事情应该可以通过某种方式实现。我还没有弄清楚,这让我觉得我错过了谜题的一些基本部分和/或推理自己进入了方案(嵌套/递归宏)的某个黑暗角落,我不应该参与其中因为有更简单的解决方案可以实现我想要的。

我不确定你是否可以用
macros scheme directed-acyclic-graphs hygiene
1个回答
0
投票
做你想做的事 - 如果可以的话,想出的东西可能又长又复杂又乏味,但是使用

syntax-rules

 来验证参数的结构和类型宏,在单个宏中就变得相当容易。
注意我更改了 
syntax-rules

子句的语法

link

(link ((from-node . from-pin) . (to-node . to-pin)))
; Same as (link ((from-node . from-pin) to-node . to-pin))

更简单、更容易使用。 2 元素对列表的列表也存储在生成的图形记录中。您还可以考虑对所有这些标识符使用符号而不是字符串,这在 lisp 家族语言中更为惯用。

(link (from-node . from-pin) (to-node . to-pin))

© www.soinside.com 2019 - 2024. All rights reserved.