“语言对代码的认知意识”方面,我所见过的最好的例子莫过于Lisp及其宏功能——具体来说是Common Lisp。但其中的取舍是,在大多数情况下,对象的类型在编译时或宏展开时并不知道。对于字面量,类型是已知的,因此可以找到一些侵略性的宏示例,测试对象是否为字面量,如果是,则以某种方式处理它——也许是基于其类型——否则准备检测变量以进行运行时类型检查。
以下是我几年前从CLLIB库(
CLOCC库的一部分)改编的一个示例。目标是提供函数,用于从具有匹配前缀的其他字符串中截取前缀字符串。前缀可能在宏展开时已知,也可能不知道。如果已知,则可以进行优化:首先计算前缀的长度并将其嵌入为字面量,以便在每次调用生成的函数时不会重新计算。该宏起初看起来令人生畏,但实际生成的代码很小。
(defmacro after-prefix-core (comparison-op prefix string &optional length)
"Similar to cllib:string-beg-with-cs."
(flet ((chop (prefix prefix-length string string-length)
`(when (and (>= ,string-length ,prefix-length)
(,comparison-op ,prefix ,string :end2 ,prefix-length))
(subseq ,string ,prefix-length ,string-length))))
(let* ((gstring (gensym "STRING-"))
(gstring-length (gensym "STRING-LENGTH-")))
`(let* ((,gstring ,string)
(,gstring-length ,(or length `(length ,gstring))))
,(if (stringp prefix)
;; Constant -- length known at expansion time.
(let ((prefix-length (length prefix)))
(chop prefix prefix-length gstring gstring-length))
;; Other form -- length not known at expansion time.
(let ((gprefix (gensym "PREFIX-"))
(gprefix-length (gensym "PREFIX-LENGTH-")))
`(let* ((,gprefix ,prefix)
(,gprefix-length (length ,gprefix)))
,(chop gprefix gprefix-length gstring gstring-length))))))))
(defmacro after-prefix (prefix string &optional length)
"Similar to cllib:string-beg-with."
`(after-prefix-core string-equal ,prefix ,string ,length))
(defmacro after-prefix-cs (prefix string &optional length)
"Similar to cllib:string-beg-with-cs."
`(after-prefix-core string= ,prefix ,string ,length))
看一下表格。
(if (stringp prefix)
“在中间”?这是在宏展开时检查第一个参数,根据参数是文字还是符号,其类型可能已知或未知。如果类型是符号,我们“假设”应该等到运行时将其重新考虑为指向某个其他值的变量。
下面是表达式“(after-prefix foo bar)”的展开:
(LET* ((#:STRING-5340 BAR) (#:STRING-LENGTH-5341 (LENGTH #:STRING-5340)))
(LET* ((#:PREFIX-5342 FOO) (#:PREFIX-LENGTH-5343 (LENGTH #:PREFIX-5342)))
(WHEN
(AND (>= #:STRING-LENGTH-5341 #:PREFIX-LENGTH-5343)
(STRING-EQUAL #:PREFIX-5342 #:STRING-5340 :END2 #:PREFIX-LENGTH-5343))
(SUBSEQ #:STRING-5340 #:PREFIX-LENGTH-5343 #:STRING-LENGTH-5341))))
请注意,变量
#:PREFIX-LENGTH-5343
绑定到
FOO
的计算长度,这里绑定到变量
#:PREFIX-5342
。
现在看看表单
(after-prefix "foo" bar)
的扩展,其中前缀现在是一个字符串字面量:
(LET* ((#:STRING-5463 BAR) (#:STRING-LENGTH-5464 (LENGTH #:STRING-5463)))
(WHEN (AND (>= #:STRING-LENGTH-5464 3) (STRING-EQUAL "foo" #:STRING-5463 :END2 3))
(SUBSEQ #:STRING-5463 3 #:STRING-LENGTH-5464)))
现在无法计算“foo”的长度,因为它被内联为3。
在这个例子中可能看起来有点麻烦,但是能够做这样的事情是一个很好的能力,正如你的问题所提到的那样。
if
表达式,如果可以确定,则会删除未使用的代码。不确定这是否完全回答了您的问题。 - Mark Ransom