Visions of the past are referred to in the Python language standard’s “Comparisions” chapter

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

That is, to write an expression as a < b &&b < C in C, Python can write it as a < b < C. Also, the standard says

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 commonly referred to as a short circuit. Therefore, expressions like 2 < 1 < (1/0) do not raise an exception in Python, but return False.

Python’s less-than sign has short-circuited properties because it is not a normal function, but rather a language-backed operator. In Common Lisp (CL), the less-than sign is just a function, just as the less-than sign is a function in Haskell. The difference is that the less-than sign of CL can accept more than two arguments

(< 1 2 3 - 1) ; The result is NIL
Copy the code

But it has no short-circuit characteristics

(< 1 2 3 - 1 (/ 1 0)) ; Raises an error named division-by-zero
Copy the code

In order to simulate the less than sign with short circuit characteristics, we must use the power of macros.

What kind of code do you want to generate

To write a macro, you must first visualize its syntax and what code it will unfold into. Let’s just name this macro less-than, and its syntax should be

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

There are a number of options for how it unfolds. For example, (less-than 2 1 (/ 1 0)) can be expanded to form and with its own short-circuit property

(and (< 2 1) (< 1 (/ 1 0)))
Copy the code

But just like the MAX macro implemented in C that computes the maximum of both with macros, the above expansion can lead to repeated evaluation in some cases

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

Therefore, at least expand the collocation of “and” and “let”

(let ((g917 1)
      (g918 (progn (print 'hello) 2)))
  (and (< g917 g918)
       (let ((g919 3(a))< g918 g919))))
Copy the code

To expand to this structure, less-than can be implemented as follows

(defmacro less-than (form &rest more-forms)
  (labels ((aux (lhs forms)
             "LHS represents the next left operand to be compared that is less than the sign."
             (unless forms
               (return-from aux))
             (let* ((rhs (gensym(a))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)))))))
Copy the code

Use the input above to verify that this results in a double evaluation

CL-USER> (macroexpand-1 '(less-than 1 (progn (print 'hello) 2) 3(a))LET ((#:G942 1(a))LET ((#:G943 (PROGN (PRINT 'HELLO) 2)))
    (AND (< #:G942 #:G943) (< #:G943 3))))
T
Copy the code

To optimize the

Obviously less-than can be optimized by simply using recursive techniques

(defmacro less-than (form &rest more-forms)
  (cond ((< = (length more-forms) 1)
         `(< ,form ,@more-forms))
        (t
         (let ((lhs (gensym(a))rhs (gensym)))
           `(let ((,lhs ,form)
                  (,rhs ,(first more-forms)))
              (and (< ,lhs ,rhs)
                   (less-than ,rhs ,@(rest more-forms))))))))
Copy the code

The expanded code is much shorter

CL-USER> (macroexpand-1 '(less-than 1 (progn (print 'hello) 2) 3(a))LET ((#:G955 1) (#:G956 (PROGN (PRINT 'HELLO) 2)))
  (AND (< #:G955 #:G956) (LESS-THAN #:G956 3)))
T
Copy the code

Read the original