Prefix - Support for Interfaces

Wednesday, August 24th, 2011     #javascript #everything

Prefix now supports interfaces and is cause for the first bit of "runtime support" code to be generated by the compiler.

This post is about Prefix a modern language that compiles to JavaScript. A language where C# is the inspiration - not the goal. Read more on the Prefix Project Page.

Interfaces in Prefix work very similarly to interfaces in C#:

  • interfaces can have functions (methods) and properties
  • interface members don't have a function body
  • they can derive from other interfaces
  • they can't have fields
  • all members are implicitly public
  • a class that implements an interface must implement all interface and base-interfaces members.
  • explicit interfaces are not supported - primarily because it would require routing method calls through a proxy object in JavaScript which seemed overkill.

As for the generated JavaScript, there's a few interesting things going on here:

{{C#}}
interface I1
{
}

interface I2
{
}

class MyObject : I1, I2
{
}
{{JavaScript}}
(function() {

var $dex =
{
  indexOf: Array.prototype.indexOf || function(item) {
        for (var i = 0; i < this.length; i++) {
         if (this[i] === item)
               return i;
       }
       return -1;
  },
  queryInterface: function(inst,itf) { 
       return inst!=null && inst.$interfaces && $dex.indexOf.call(inst.$interfaces, itf)>=0 ? inst: null; 
 }
};

// interface I1
var I1 = {};

// interface I2
var I2 = {};

// class MyObject
var MyObject = function()
{

};

// MyObject interfaces
MyObject.prototype.$interfaces = [I1, I2];



})();

Firstly, note that each interfaces is declared as a simple empty object (ie: without member declarations). The interface declaration is simply to get the name of the interface into the code and acts as a way to identify the type:

eg:

// interface I1
var I1 = {};

Next, a class that implements one or more interfaces has those interface names listed out in the prototype as a new field $interfaces. This list includes all base interfaces so there's no need to walk the parent chain to determine if an interface is supported.

eg:

// MyObject interfaces
MyObject.prototype.$interfaces = [I1, I2];

This is the first time we've seen the compiler write supporting run-time functions:

  • $dex.queryInterface - checks if an object implements an interface
  • $dex.indexOf - because not all browsers (IE I'm looking at you!) support Array.indexOf which is used by the queryInterface implementation.

Finally, the various kinds of "to interface" casts are implemented using the new queryInterface function:

{{C#}}
I1 i1 = new MyObject();
I2 i2 = i1 as I2;
{{JavaScript}}
var i1=new MyObject();
var i2=$dex.queryInterface(i1, I2);

And that's it. Thanks to the dynamic nature of JavaScript there's no run-time cost to calling an interface method and all type checking is done at compile time.

« Older - Unit Testing Prefix with V8, V8Sharp and PetaTest (and hints on what's coming next) Newer - A Language (temporarily) called Prefix »