Prefix - Generics (again), indexers, read only fields, variadic parameters

Friday, September 16th, 2011     #javascript #everything

In this last week or so I've been working on getting Prefix to the point where arrays are working. Unfortunately this highlighted a couple of design flaw in generics...

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.

Infinitely Recursive Generic Types

In my previous post I suggested that I had generics working. Well not quite and I've had to do a major reworking of it. The first problem was that with generics it's possible to have an infinitely recursive type definition. Consider this example:

class MyClass<T>
{
    MyClass<MyClass<T>> SomeFunction();
}

Now imagine you create the closed type MyClass<int>. The return value from the function will be MyClass<MyClass<int>> - but that type has a SomeFunction method that returns MyClass<MyClass<MyClass<int>>>. And so on...

Prefix's original generic implementation tried to fully close generic types as soon as they were used - so the above immediately crashed with a stack overflow exception as it recursively tried to make those closed types.

Of course the fix was simple once I understood it - just delay making those closed types until needed.

You'd think the above is a weird edge case that would never happen. Ironically it cropped up almost the first time I tried to use generics (to declare Prefix's array class).

Nested Generics Might Not Be What You Think

Probably the most difficult part of getting generics working was nested generic classes - that is until I understood what they really are (not what I thought they were). Originally I presumed that on making a closed generic class any inner nested classes would become part of that newly generated closed class.

However that is not the case - at least when you look at how C# does it. In C# a nested generic class is simply a generic class that inherits the same set of generic parameters as the outer class and tacks its own generic parameter on the end.

So this class:

class Outer<T>.Inner<U>

is really implemented as:

class Outer.Inner<T,U>

Discovering this helped solve a whole class of problems with generics but required a fair bit of reworking the code - though pleasingly the generated JavaScript is now simpler and slightly smaller.

Read Only Fields

Now that generics were working properly, I was at the point that I could start looking at what else was required to get arrays working. First up was readonly fields (since the length property of an array should be readonly).

Read only fields work identically to C# readonly fields. They can be initialized via a direct initializer or in the class constructor.

class Foo
{
    public Foo()
    {
        val="Hello World";
    }

    public readonly string val;
}

I haven't included the generated JavaScript code here because the enforcement of readonly-ness is done at compile time - not runtime. You could get around this by casting to a dynamic if you really wanted.

Indexers

Arrays are fairly useless without indexers. Indexers are implemented like properties with parameters. Just like C# they can be overloaded, but Prefix doesn't support variadic (parameter array) indexers. In JavaScript they're implemented as methods $get_at and $set_at.

{{C#}}
extern dynamic Console;

class Foo
{
    public string this[int index]
    {
        get
        {
            Console.WriteLine("get[" + index.toString() + "]");
            return index.toString();
        }
        set
        {
            Console.WriteLine("set[" + index.toString() + "] = " + value);
        }
    }
}

var f = new Foo();
Console.WriteLine(f[99]);
f[99]="newval";
{{JavaScript}}
(function() {

// class Foo
var Foo = function() {};
Foo.typeName = 'Foo';

Foo.prototype.$get_at = function(index)
{
    Console.WriteLine("get[" + index.toString() + "]");
    return index.toString();
};

Foo.prototype.$set_at = function(index, value)
{
    Console.WriteLine("set[" + index.toString() + "] = " + value);
    return value;
};

// Start Global Code
var f=new Foo();
Console.WriteLine(f.$get_at(99));
f.$set_at(99, "newval");

})();

Variadic Parameters

The JavaScript array class supports passing a variable number of arguments to some of it's methods - commonly called variadic functions.

In C# variadic methods can be called in two ways - normal form and expanded form. For example, this function:

int Sum(params int[] values);

can be called like this:

// Expanded form
Sum(1,2,3,4);

or by passing an array directly:

// Normal form
var values = new int[] { 1, 2, 3, 4 };
Sum(values);

To replicate this in JavaScript, if the passed variable is an array it's assumed to be a normal form call and used directly, otherwise the JavaScript arguments are sliced to create an array.

{{C#}}
extern dynamic Console;

int sum(int first, params int[] other)
{
    for (int i=0; i<other.length; i++)
  {
       first += other[i];
  }
   return first;
}

Console.WriteLine(sum(1,2,3,4));
{{JavaScript}}
(function() {

function sum(first, other)
{
  if (!(other instanceof Array)) other = Array.prototype.slice.call(arguments, 1);
    for (var i=0; i<other.length; i++)
  {
       first += other[i];
  }
   return first;
};

// Start Global Code
Console.WriteLine(sum(1, 2, 3, 4));

})();

Of course using this technique to detect whether the function is being called in normal or expanded form doesn't work for arrays of arrays (since there isn't enough type information in JavaScript at runtime) to work this out. In this case Prefix disallows the short form calling and generates an error.

Actually, Prefix does a slightly better job than shown above. If the target function is never called in short form, the if statement is omitted and arguments is sliced directly.

Conclusion

I now have everything to get arrays working... which I'll talk about in the next post.

« Older - Prefix - Arrays Newer - Prefix takes One Giant Leap with Support for Generics »