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;
}

Friday, December 6, 2013

PythonJS Dart Backend


I have started working on a second backend for PythonJS that outputs Dart code. Dart is a new language by Google that is very similar to JavaScript, it includes all the things missing from JavaScript like: classes, static types, operator overloading, and a compiler that checks all your code. Using dart2js you can translate Dart code into JavaScript code so that it works in all web browsers.

One of the limitations of Dart is that it lacks multiple inheritance, [1], [2]. It features Mix-ins and Interfaces, but these can not fully capture all the power that proper multiple inheritance provides. Using PythonJS you can bypass this limitation of Dart and use multiple inheritance.

Multiple Inheritance

PythonJS implements multiple inheritance in Dart using: static class methods, stub-methods, and interfaces. The method body is placed inside static class methods. The stub-methods are real methods on the instance, and simply forward calls to the static class methods and pass this as the first argument. Sub-classes can extend a method of the parent, and still call the parent's method using the normal Python syntax: parent.some_method(self).

Python Input

class A:
 def foo(self):
  print 'foo'

class B:
 def bar(self):
  print 'bar'

class C( A, B ):
 def call_foo_bar(self):
  print 'call_foo_bar in subclass C'
  self.foo()
  self.bar()

 ## extend foo ##
 def foo(self):
  A.foo(self)
  print 'foo extended'

Dart Output

class A {
  foo() { return A.__foo(this); }
  static __foo(self) {
    print("foo");
  }
}

class B {
  bar() { return B.__bar(this); }
  static __bar(self) {
    print("bar");
  }
}

class C implements A, B {
  call_foo_bar() { return C.__call_foo_bar(this); }
  static __call_foo_bar(self) {
    print("call_foo_bar in subclass C");
    self.foo();
    self.bar();
  }

  foo() { return C.__foo(this); }
  static __foo(self) {
    A.__foo(self);
    print("foo extended");
  }

  bar() { return B.__bar(this); }
}

Monday, December 2, 2013

JavaScript Yield


Firefox has had support for the yield keyword for a long time already, but Google Chrome is only just recently supporting this features as an experimental option. Basically, this means if you want your website to work in most web browsers, you can forget about using yield and the beauty of generator functions for the next couple years, it is going to take awhile before the majority of users upgrade. It is time to give up on yield? There is one option...

PythonJS now supports generator functions that will work in all browsers by translating the generator into a class with state-machine at compile time. Using PythonJS to write generator functions also produces JavaScript that runs faster than hand-written JavaScript that uses the native yield keyword. This is likely because the native yield is still a rarely used feature, and JIT's have not tuned their performance for it. PythonJS translates generator functions into simple classes with a next method, which is very JIT friendly. The results below show native yield is more than 10 times slower than yield in PythonJS. I was unable to test native yield in Chrome, for some reason I could not get it to work even after switching on harmony.

The Fibonacci series computed 1,000 times to 1,000 places in Firefox28, Python2.7, PyPy2.2, and GoogleChrome. Lower times are better. The code used in this benchmark is below: