反向运算和增量赋值

来源:互联网 发布:matlab 矩阵范数 编辑:程序博客网 时间:2024/05/16 14:37
如果抛开具体实现讨论理论,就有些太过抽象,所以下面和之后如无特殊说明,默认以larva的目前实现为背景,即python实现的编译器和转成java执行


CPU只能直接处理很基础的数据类型,如果简化一下,可以归类为寻址、整数(一般在ALU、乘法器、除法器、移位器等计算,含指针/地址计算)和浮点数(一般有专门的FPU),一个程序可能有成百上千的类型,但归根到底都是由地址、整数、浮点数等组成的复合类型(class或struct)


按照之前说过的动态类型语言的设计,所有数据类型都是一个object的子类,则上述基础类型都应该包装起来,所以不能直接做计算,解决办法,就是用方法调用来实现计算,比如larva的做法,~a实现为a.op_invert(),a+b实现为a.op_add(b),其他计算类似


由于a和b引用对象的实际类型可能是任意类型,只能在调用接口的时候动态判定,比如,a和b都是int型,在a.op_add方法里面判断,如果b是int,则进行整数加法,并返回一个表示和的整数对象,返回类型是object,但如果a是int,b是float,则又需要一个判断,同样在a是float、b是int的时候,在float类的op_add方法中也要做判断,这就很麻烦,更麻烦的是,如果我们要新增一个数值类型,比方说复数,就得修改之前所有的可能混合运算的类型,int、long、float等,数字之外的类型也是类似的,比如tuple、list、string等序列类型都支持和整数的乘法运算,比如"ab"*3结果是"ababab",若这个运算满足交换律,则int的op_mul方法里面还得判断是否上述三种序列函数,这不但麻烦而且低效


在这个问题上,larva借鉴了python的做法,引入反向运算,即对于a+b,可以有两种实现,a.op_add(b)或b.op_reverse_add(a),前者优先,也就是说,先试着计算前者,不行了再算后者,事实上并没有一个“尝试”的过程,而是在object类中这样实现:
LarObj op_add(LarObj obj){    return obj.op_reverse_add(this);}
即若类型没有覆盖这个方法,则自动路由至反向运算的实现,显然反向运算若没有实现,不能路由回正向运算,而应该抛出一个错误,否则会无限递归


接下来的问题就是,什么时候使用反向运算,一般而言可以这样规定:对于二元运算来说,两个运算分量根据结果类型或两者类型决定运算的“发起方”,换句话说就是给类型“定级”,并规定,一个类型的正向运算方法中只需考虑和它同级或比它级别低的类型,反向运算方法中只需考虑比它级别低的类型


比如,我们规定级别由低到高是int、long、float,则int的op_add等二元运算中,只需要判断参数是否为int,若不是,则直接调用传入参数的反向运算,即int和long运算由long发起,无论他们的位置如何,这样一来就简化了实现和代码管理


对于这种一个语法可能根据实际情况有不同的实现,larva中还有个例子,例:a.f(),这句看上去是调用对象a的方法f,但也可能是取a的f属性,并对对象做call运算,动态类型语言中,很难在编译阶段检查出是哪种情况,因此只能在运行时根据实际情况来判断,做法和上面类似,先假设其中一种情况,若没有实现则路由至另一种情况的处理


对这种情况,python规定这个表达式是先取属性f,再执行call运算,因此,若a只有f方法,则python会构造一个method对象,对其做call,然后释放,这样做的好处是,a.f可以求值为一个方法对象,就像类和函数都是对象一样,坏处就是效率比较低


larva采用了另一种方案,对于这个表达式,认为其语法上是调用a的f方法,运行时若a没有f方法,则路由至取f属性并调用:
LarObj meth_f(){    return op_get_attr_f().op_call();}
如果连f属性都没有,就异常。这样做的原因是,方法调用可以拆为“取属性”和“调用”两个步骤,但反过来取属性操作不能分解为调用,如果采用python的方式,就必须引入临时对象,或将所有方法都改成可调用对象,会降低效率
另一个原因是,从经验看,这个表达式在绝大多数情况下都是方法调用,对属性做call运算的情况很少,所以优先采用可能性高的处理方式也能提高效率


关于运算另一个问题是增量赋值,即a+=b这类,语法上一个运算符,但是需要两个步骤,先计算再赋值,而且根据运算分量类型不同,分两种情况:
1 a不可改变,a+=b相当于a=a+b
2 对a类型的+=运算定义有别于情况1,例如a是一个list,则a+=b相当于将b的内容添加到a的末尾,而此时a=a+b相当于重新建立一个列表然后赋值给a


和上述方法调用的处理类似,我们可以找到一个方式兼容这两种情况,具体的以+=为例,定义一个运算方法op_inplace_add,于是代码a+=b转换为运算:
a=a.op_inplace_add(b)
可以看到,这其实就是一个普通的a=a op b的模式,先看情况1,只需要将op_inplace_add实现为和op_add相同即可,实际上其默认行为就是直接路由到op_add,这样一来对于大部分符合情况1的类型,就无需实现op_inplace_*系列操作了
而对于情况2,只需规定一个类型的op_inplace_add操作返回自身,即return this,这样一来最后一个赋值等于没做,就符合了情况2的需求


最后一个问题,若a不是一个简单变量,则上述转换会造成求值问题,比如a[f()]+=b若按上述转换,f()会被执行两次,但原代码的语义是只执行一次,类似f().a+=b也有这个问题。因此在碰到左值是[]或“.”运算时,需要一个临时变量先计算左值的分量,然后再做上述转换,即如f()[g()]+=b转换为:
tmp_a = f()tmp_b = g()tmp_a.set_item(tmp_b, tmp_a.get_item(tmp_b).op_inplace_add(b))//下标取值赋值也要转成函数


larva中,只有普通变量、[]和“.”三种运算可以用于增量赋值的左值,其余左值(解包赋值的左值形式、分片赋值左值形式)等均不可用于增量赋值
0 0
原创粉丝点击