头图

past eventful years thick in the Python language standard of Comparisions chapter mentioned in

Also unlike C, expressions like a < b < c have the interpretation that is conventional in mathematics

In other words, to be written in the C language a < b && b < c expressions can be written in Python a < b < c . And, the standard also mentions

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).

This property is generally referred to as a short circuit. Therefore, 2 < 1 < (1 / 0) will not raise an exception in Python, but instead return False .

Python's less than sign can have short-circuit characteristics because it is not an ordinary function, but an operator supported by the language. In Common Lisp (hereinafter referred to as CL), the less than sign is just an ordinary function, just like the less than sign in Haskell is also a function. The difference is that CL's less than sign can accept more than two parameters

(< 1 2 3 -1) ; 结果为NIL

But it does not have short circuit characteristics

(< 1 2 3 -1 (/ 1 0)) ; 引发名为DIVISION-BY-ZERO的错误

If you want to simulate the less than sign with short-circuit characteristics, you must rely on the power of macros.

What kind of code do you want to generate

To write a macro, you must first imagine its syntax and what kind of code it will expand into. less-than name this macro 060d6b463a03ab, and its syntax should be

(defmacro less-than (form &rest more-forms)
  ; TBC
  )

There are many options for its expansion result. For example, (less-than 2 1 (/ 1 0)) can be expanded into the form and with short circuit characteristics.

(and (< 2 1) (< 1 (/ 1 0)))

MAX macro that calculates the maximum value of the two in the C language, the above expansion method will lead to repeated evaluation in some cases

(less-than 1 (progn (print 'hello) 2) 3)

Therefore, at least it should be expanded into a combination and and let

(let ((g917 1)
      (g918 (progn (print 'hello) 2)))
  (and (< g917 g918)
       (let ((g919 3))
         (< g918 g919))))

If you want to expand into this structure, you can implement 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)))))))

Use the above input to verify whether it will cause repeated evaluation

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

Optimize

Obviously less-than can be optimized, just simply use recursive techniques.

(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))))))))

The expanded code is much shorter

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

Read the original text


用户bPGfS
169 声望3.7k 粉丝