Tuesday, December 10, 2013

PythonJS and Dart - Operator Overloading


PythonJS now translates operator overloading from Python to the Dart backend, see my commit here. Dart supports all the same operators as Python, except for in-place operators. In-place operators are very useful, I am curious why the Dart developers have left this feature out?

Even if this feature is missing in Dart, it can still be forced to work when PythonJS translates to Dart code, by inlining a special if/else for each in-place assignment. Example x += y
if x is a Number or String: then use +=: else use x.__iadd__(y)

The Dart2js compiler is smart enough to determine the type of x at compile time, and optimize the if/else away. This opens the door for doing more dumb tricks from PythonJS that get optimized away by Dart2js.

Python Input

class Vector3:
 def __init__(self, x=0, y=0, z=0 ):
  self._x = x
  self._y = y
  self._z = z

 def set(self, x,y,z):
  self._x = x
  self._y = y
  self._z = z

 @property
 def x(self):
  return self._x
 @x.setter
 def x(self, value):
  print 'x setter', value
  self._x = value

 @property
 def y(self):
  return self._y
 @y.setter
 def y(self, value):
  print 'y setter', value
  self._y = value

 @property
 def z(self):
  return self._z
 @z.setter
 def z(self, value):
  print 'z setter', value
  self._z = value


 def add(self, other):
  self.set( self.x+other.x, self.y+other.y, self.z+other.z )
  return self

 def __add__(self, other):
  if instanceof(other, Number):
   return Vector3( self.x+other, self.y+other, self.z+other )
  else:
   return Vector3( self.x+other.x, self.y+other.y, self.z+other.z )

 def __iadd__(self, other):
  if instanceof(other, Number):
   self.addScalar( other )
  else:
   self.add( other )

 def addScalar(self, s):
  self.set( self.x+s, self.y+s, self.z+s )
  return self


 def sub(self, other):
  self.set( self.x-other.x, self.y-other.y, self.z-other.z )
  return self

 def __sub__(self, other):
  if instanceof(other, Number):
   return Vector3( self.x-other, self.y-other, self.z-other )
  else:
   return Vector3( self.x-other.x, self.y-other.y, self.z-other.z )

 def __isub__(self, other):
  if instanceof(other, Number):
   self.set( self.x-other, self.y-other, self.z-other )
  else:
   self.sub( other )


 def multiply(self, other):
  self.set( self.x*other.x, self.y*other.y, self.z*other.z )
  return self

 def __mul__(self, other):
  if instanceof(other, Number):
   return Vector3( self.x*other, self.y*other, self.z*other )
  else:
   return Vector3( self.x*other.x, self.y*other.y, self.z*other.z )

 def __imul__(self, other):
  if instanceof(other, Number):
   self.multiplyScalar( other )
  else:
   self.multiply( other )

 def multiplyScalar(self, s):
  self.set( self.x*s, self.y*s, self.z*s )
  return self

 def divide(self, other):
  self.set( self.x/other.x, self.y/other.y, self.z/other.z )
  return self

 def divideScalar(self, s):
  self.set( self.x/s, self.y/s, self.z/s )
  return self

 def __div__(self, other):
  if instanceof(other, Number):
   return Vector3( self.x/other, self.y/other, self.z/other )
  else:
   return Vector3( self.x/other.x, self.y/other.y, self.z/other.z )

 def __idiv__(self, other):
  if instanceof(other, Number):
   self.divideScalar( other )
  else:
   self.divide( other )


def show_vec(v):
 print '-------------'
 print v.x
 print v.y
 print v.z

def main():
 n = 1
 n += 2
 print n

 v1 = Vector3(1, 2, 3)
 v2 = Vector3(10, 0, 10)
 print v1, v2
 print 'testing +'
 a = v1 + v2
 show_vec(a)

 print 'testing +='
 a += 2.5
 show_vec(a)
 a += v1
 show_vec(a)

 print 'testing -='
 a -= v1
 show_vec(a)
 a -= 100
 show_vec(a)

 print 'testing *'
 b = v1 * v2
 show_vec(b)

 print 'testing *='
 b *= 10.0
 show_vec(b)
 b *= v2
 show_vec(b)

 print 'testing setters'
 b.x = 1
 b.y = 2
 b.z = 3

Dart Output


class Vector3 {
  var _z;
  var _y;
  var _x;
  static void __init__(self, [x=0,y=0,z=0]) {
    self._x = x;
    self._y = y;
    self._z = z;
  }

  Vector3(x,y,z) {Vector3.__init__(this,x,y,z);}
  set(x,y,z) { return Vector3.__set(this,x,y,z); }
  static __set(self, x, y, z) {
    self._x = x;
    self._y = y;
    self._z = z;
  }

  get x {
    return this._x;
  }

  set x(value) {
    print(["x setter", value]);
    this._x = value;
  }

  get y {
    return this._y;
  }

  set y(value) {
    print(["y setter", value]);
    this._y = value;
  }

  get z {
    return this._z;
  }

  set z(value) {
    print(["z setter", value]);
    this._z = value;
  }

  add(other) { return Vector3.__add(this,other); }
  static __add(self, other) {
    self.set((self.x + other.x), (self.y + other.y), (self.z + other.z));
    return self;
  }

  operator +(other) { return Vector3.____add__(this,other); }
  static ____add__(self, other) {
    if (other is num) {
      return  new Vector3((self.x + other), (self.y + other), (self.z + other));
    } else {
      return  new Vector3((self.x + other.x), (self.y + other.y), (self.z + other.z));
    }
  }

  __iadd__(other) { return Vector3.____iadd__(this,other); }
  static ____iadd__(self, other) {
    if (other is num) {
      self.addScalar(other);
    } else {
      self.add(other);
    }
  }

  addScalar(s) { return Vector3.__addScalar(this,s); }
  static __addScalar(self, s) {
    self.set((self.x + s), (self.y + s), (self.z + s));
    return self;
  }

  sub(other) { return Vector3.__sub(this,other); }
  static __sub(self, other) {
    self.set((self.x - other.x), (self.y - other.y), (self.z - other.z));
    return self;
  }

  operator -(other) { return Vector3.____sub__(this,other); }
  static ____sub__(self, other) {
    if (other is num) {
      return  new Vector3((self.x - other), (self.y - other), (self.z - other));
    } else {
      return  new Vector3((self.x - other.x), (self.y - other.y), (self.z - other.z));
    }
  }

  __isub__(other) { return Vector3.____isub__(this,other); }
  static ____isub__(self, other) {
    if (other is num) {
      self.set((self.x - other), (self.y - other), (self.z - other));
    } else {
      self.sub(other);
    }
  }

  multiply(other) { return Vector3.__multiply(this,other); }
  static __multiply(self, other) {
    self.set((self.x * other.x), (self.y * other.y), (self.z * other.z));
    return self;
  }

  operator *(other) { return Vector3.____mul__(this,other); }
  static ____mul__(self, other) {
    if (other is num) {
      return  new Vector3((self.x * other), (self.y * other), (self.z * other));
    } else {
      return  new Vector3((self.x * other.x), (self.y * other.y), (self.z * other.z));
    }
  }

  __imul__(other) { return Vector3.____imul__(this,other); }
  static ____imul__(self, other) {
    if (other is num) {
      self.multiplyScalar(other);
    } else {
      self.multiply(other);
    }
  }

  multiplyScalar(s) { return Vector3.__multiplyScalar(this,s); }
  static __multiplyScalar(self, s) {
    self.set((self.x * s), (self.y * s), (self.z * s));
    return self;
  }

  divide(other) { return Vector3.__divide(this,other); }
  static __divide(self, other) {
    self.set((self.x / other.x), (self.y / other.y), (self.z / other.z));
    return self;
  }

  divideScalar(s) { return Vector3.__divideScalar(this,s); }
  static __divideScalar(self, s) {
    self.set((self.x / s), (self.y / s), (self.z / s));
    return self;
  }

  operator /(other) { return Vector3.____div__(this,other); }
  static ____div__(self, other) {
    if (other is num) {
      return  new Vector3((self.x / other), (self.y / other), (self.z / other));
    } else {
      return  new Vector3((self.x / other.x), (self.y / other.y), (self.z / other.z));
    }
  }

  __idiv__(other) { return Vector3.____idiv__(this,other); }
  static ____idiv__(self, other) {
    if (other is num) {
      self.divideScalar(other);
    } else {
      self.divide(other);
    }
  }

}

show_vec(v) {
  print("-------------");
  print(v.x);
  print(v.y);
  print(v.z);
}

main() {
  var a, v1, v2, b, n;
  n = 1;
  if (n is num || n is String) {
    n += 2;
  } else {
    n.__iadd__(2);
  }
  print(n);
  v1 =  new Vector3(1, 2, 3);
  v2 =  new Vector3(10, 0, 10);
  print([v1, v2]);
  print("testing +");
  a = (v1 + v2);
  show_vec(a);
  print("testing +=");
  if (a is num || a is String) {
    a += 2.5;
  } else {
    a.__iadd__(2.5);
  }
  show_vec(a);
  if (a is num || a is String) {
    a += v1;
  } else {
    a.__iadd__(v1);
  }
  show_vec(a);
  print("testing -=");
  if (a is num || a is String) {
    a -= v1;
  } else {
    a.__isub__(v1);
  }
  show_vec(a);
  if (a is num || a is String) {
    a -= 100;
  } else {
    a.__isub__(100);
  }
  show_vec(a);
  print("testing *");
  b = (v1 * v2);
  show_vec(b);
  print("testing *=");
  if (b is num || b is String) {
    b *= 10.0;
  } else {
    b.__imul__(10.0);
  }
  show_vec(b);
  if (b is num || b is String) {
    b *= v2;
  } else {
    b.__imul__(v2);
  }
  show_vec(b);
  print("testing setters");
  b.x = 1;
  b.y = 2;
  b.z = 3;
}

1 comment:

  1. "In-place operators are very useful, I am curious why the Dart developers have left this feature out?"

    I remember seeing somewhere that they wanted x += 5 to always be equivalent to x = x + 5. So overloading +, implicitly overloads +=.

    ReplyDelete