共计 1902 个字符,预计需要花费 5 分钟才能阅读完成。
忆往昔峥嵘岁月稠 在 Python 的语言规范的 Comparisions 章节中提到
Also unlike C, expressions like a < b < c have the interpretation that is conventional in mathematics
也就是说,在 C 语言中要写成 a < b && b < c
的表达式,在 Python 中能够写成a < b < c
。并且,规范中还提到
Comparisons can be chained arbitrarily, e.g., x < y <= z is equivalent to x < y and y <= z, except that y is evaluated only once (but in both cases z is not evaluated at all when x < y is found to be false).
个别将这种性质成为短路。因而,像 2 < 1 < (1 / 0)
这样的表达式在 Python 中不会引发异样,而是返回False
。
Python 的小于号能领有短路个性,是因为它并非一个一般函数,而是有语言层面加持的操作符。而在 Common Lisp(下称 CL)中,小于号仅仅是一个一般函数,就像 Haskell 中的小于号也是一个函数个别。不同的是,CL 的小于号能承受多于两个的参数
(< 1 2 3 -1) ; 后果为 NIL
但它并没有短路个性
(< 1 2 3 -1 (/ 1 0)) ; 引发名为 DIVISION-BY-ZERO 的谬误
要想模拟出具备短路个性的小于号,必须借助于宏的力量。
想生成什么样的代码
要想写出一个宏,必须先构想出它的语法,以及它会开展成什么样的代码。权且为这个宏起名为less-than
,它的语法该当为
(defmacro less-than (form &rest more-forms)
; TBC
)
至于它的开展后果能够有多种抉择。例如,能够 (less-than 2 1 (/ 1 0))
开展为本身具备短路个性的 and
模式
(and (< 2 1) (< 1 (/ 1 0)))
但就像在 C 语言中用宏奢侈地实现计算二者最大值的 MAX
宏一样,下面的开展形式在一些状况下会导致反复求值
(less-than 1 (progn (print 'hello) 2) 3)
因而,起码要开展为 and
和let
的搭配
(let ((g917 1)
(g918 (progn (print 'hello) 2)))
(and (< g917 g918)
(let ((g919 3))
(< g918 g919))))
要想开展为这种构造,能够如这般实现less-than
(defmacro less-than (form &rest more-forms)
(labels ((aux (lhs forms)
"LHS 示意紧接着下一次要比拟的、小于号的左操作数。"
(unless forms
(return-from aux))
(let* ((rhs (gensym))
(rv (aux rhs (rest forms))))
(if rv
`(let ((,rhs ,(first forms)))
(and (< ,lhs ,rhs)
,rv))
`(< ,lhs ,(first forms))))))
(cond ((null more-forms)
`(< ,form))
(t
(let ((lhs (gensym)))
`(let ((,lhs ,form))
,(aux lhs more-forms)))))))
用下面的输出验证一下是否会导致反复求值
CL-USER> (macroexpand-1 '(less-than 1 (progn (print'hello) 2) 3))
(LET ((#:G942 1))
(LET ((#:G943 (PROGN (PRINT 'HELLO) 2)))
(AND (< #:G942 #:G943) (< #:G943 3))))
T
优化一下
显然 less-than
能够优化,只须要简略地使用递归的技巧即可
(defmacro less-than (form &rest more-forms)
(cond ((<= (length more-forms) 1)
`(< ,form ,@more-forms))
(t
(let ((lhs (gensym))
(rhs (gensym)))
`(let ((,lhs ,form)
(,rhs ,(first more-forms)))
(and (< ,lhs ,rhs)
(less-than ,rhs ,@(rest more-forms))))))))
开展后的代码简短得多
CL-USER> (macroexpand-1 '(less-than 1 (progn (print'hello) 2) 3))
(LET ((#:G955 1) (#:G956 (PROGN (PRINT 'HELLO) 2)))
(AND (< #:G955 #:G956) (LESS-THAN #:G956 3)))
T
浏览原文