模拟Python中小于运算符的短路特性

日志狂
• 阅读 1431

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

阅读原文

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
美凌格栋栋酱 美凌格栋栋酱
10个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
4年前
Python Challenge Level 18
初学Python,挑战一下流行的PythonChallenge,很不幸,卡在了18关~~被字符字节码之间的转换搞得焦头烂额,不过终于搞定了还是很happy的~~~主要的问题就是16进制形式的字符如何转成字节码(注意:不是encoding)如:\'89','50','4e','47','0d','0a','1a','0a','00
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
4年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Stella981 Stella981
4年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这