Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

30 Apr 2021

The Racket Reference阅读笔记【四】Macro相关

The Racket Reference阅读笔记。

Macros

12.1 Pattern-Based Syntax Matching

syntax-case语法如下:

(syntax-case stx-expr (literal-id ...)
  clause ...)

其中clause用来指定模式:

clause	 	=	 	[pattern result-expr]
 	 	|	 	[pattern fender-expr result-expr]

pattern则是:

pattern	 	=	 	np-pattern
 	 	|	 	(pattern ...)
 	 	|	 	(pattern ...+ . np-pattern)
 	 	|	 	(pattern ... pattern ellipsis pattern ... . np-pattern)

首先pattern可以分成括起来的模式,以及非括起来的模式(np-pattern)。

(pattern ...)表示可以有在括号内嵌套列举任意个模式,(pattern ...+ . np-pattern)类似,但是用于在某个非括起来的模式之前列举一至多个模式,也就是说不管制定了多少个模式,必须以np-pattern收尾。

对于(pattern ... pattern ellipsis pattern ...)则允许在模式中使用字面上的ellipsis,也就是...,例如a ...。在这个跟着三点的模式之前可以列举任意多个模式,在其之后也可以列举任意多个模式。当然,也可以指定一个np-pattern作为收尾模式。

np-pattern则可以表示为:

np-pattern	 	=	 	_
 	 	|	 	id
 	 	|	 	#(pattern ...)
 	 	|	 	#(pattern ... pattern ellipsis pattern ...)
 	 	|	 	#&pattern
 	 	|	 	#s(key-datum pattern ...)
 	 	|	 	#s(key-datum pattern ... pattern ellipsis pattern ...)
 	 	|	 	(ellipsis stat-pattern)
 	 	|	 	const
  • #号开头的是列项,封箱还有预制结构。
  • _ 匹配任意对象但是不生成绑定
  • id匹配…或者_或者字面值以外的标识符,并且会被绑定为模式变量。模式变量是一个转换器绑定,只能在syntax中使用,其值是一个深度标记为零的语法对象。
  • id为字面值id的情况下,跟目标字面值id具有free-identifie=?相等性,匹配的结果不生成模式变量。
  • const在np-pattern中用于收尾,如果前面的项目都不匹配,那么就会试着当做常量值来匹配(equal?同一性),也就是说,如果指定1,就会呗当做'1来目标值匹配。

(ellipsis stat-pattern)需要额外说明一下,它表现为:

stat-pattern	 	=	 	id
 	 	|	 	(stat-pattern ...)
 	 	|	 	(stat-pattern ...+ . stat-pattern)
 	 	|	 	#(stat-pattern ...)
 	 	|	 	const

其实是用于将模板中指定的...当做正常的...,而不是将其视为重复操作。

syntax-case*syntax-case类似,但是在clause之前接受一个id-compare-expr,其接受两个参量,一是字面值id,二是模板中的标识符。id-compare-expr要给出二者是否具有free-identifier=?同一性。

with-syntax,有点类似于:

(syntax-case (list stx-expr ...) ()
  [(pattern ...) (let () body ...+)])

with-syntax会对列举的每个模式进行匹配,如果模式对象的表达式返回的不是语法对象,则会通过datum->syntax将其转化为表达式。

上面列举的模式用于匹配和拆分语法对象,下面提到的syntax语法模板则用来重构语法对象。

(syntax template)

 
template	 	=	 	id
 	 	|	 	(head-template ...)
 	 	|	 	(head-template ...+ . template)
 	 	|	 	#(head-template ...)
 	 	|	 	#&template
 	 	|	 	#s(key-datum head-template ...)
 	 	|	 	(~? template template)
 	 	|	 	(ellipsis stat-template)
 	 	|	 	const
 	 	 	 	 
head-template	 	=	 	template
 	 	|	 	head-template ellipsis ...+
 	 	|	 	(~@ . template)
 	 	|	 	(~? head-template head-template)
 	 	|	 	(~? head-template)
 	 	 	 	 
stat-template	 	=	 	like template, but without ..., ~?, and ~@
 	 	 	 	 
ellipsis	 	=	 	...

syntax模板不仅能够将模式变量替换成对应的值,还能做一些额外的控制。

(ellipsis stat-template)用来关闭...~?以及~@的特殊含义。

head-template用于受括列表中的前置项(因为列表的尾项不可是head-template,否则递归无法终止。注意,head-template包含了template。也就是递归的前置项可以有更多花样。

(~? template1 template2)中,如果template1有缺失变量则应用template2,有点像C++中的SFINAE,没有有?

(~? head-template)可以生成空项。

(~@ . template)用于展开和拼接template生成的连对。

head-template ellipsis ...+有点费解,可以想象成在head-template所包含的模式变量上进行map操作。迭代的次数取决于模板中模式变量的值。如果将outer当做三点尾随的inner。如果和outer的深度标记相同,那么模式变量就是outer的迭代模式变量。至少要有一个这种类型的变量,否则就出错了。如果有多个此种类型的变量,那么必须有相同个数的元素。outer的结果是将inner模板应用到迭代模式变量而获得的,每次应用的时候将深度标记减一。ellipsis可以指定一至多个,没指定一次则深入迭代一次,但是不能超过迭代模式变量的深度。

(syntax-case #'((1 2) (3 4)) ()
    [((l ...) ...) #'(l ... ...)])
.#<syntax:interactions from an unsaved editor:22:21 (1 2 3 4)>

其他相关函数

  • (quasisyntax template)
  • (unsyntax expr)
  • (syntax/loc stx-expr template),额外指定文法信息
  • (quasisyntax/loc stx-expr template)
  • (quote-syntax/prune id),剔除id的某些作用域
  • (syntax-pattern-variable? v) → boolean?判断是否是模式变量,在位阶0好像不能用

(本篇完)