忆往昔峥嵘岁月稠在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)

因而,起码要开展为andlet的搭配

(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

浏览原文