Prefix - Delegates

Sunday, September 25th, 2011     #javascript #everything

In JavaScript, functions are "first-class objects" that can be stored in variables or passed as arguments to other functions. Prefix delegates are similar except they're strongly typed.

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.

"First-class Functions" are one of those topics that most new programmers struggle with - it's a weird concept passing a function to another function, or storing it in a variable. In JavaScript however this is a fairly common practice and the Prefix equivalent is a delegate.

Simple Delegates

A delegate type declaration gives a name to a function prototype ie: the set of parameters to a function and its return type. Once a delegate type is declared you can create variables or parameters of that type and store or pass function references in them.

eg:

{{C#}}
extern dynamic Console;

// This is the delegate type - it specifies that functions
// references by it must have a single integer paramter and
// a void return type
delegate void delDoSomethingWithInt(int a);

// Here's a function that satisfies that requirement
void PrintInt(int x)
{
 Console.WriteLine(x.toString());
}

// Create a variable and store a reference to the function in it
delDoSomethingWithInt fn = PrintInt;

// Call it
fn(5);
{{JavaScript}}
(function() {

function PrintInt(x)
{
    Console.WriteLine(x.toString());
};

// Start Global Code
var fn=PrintInt;
fn(5);

})();

Note that in the generated JavaScript all references to the delegate type vanish - however they've served there purpose in letting the compiler check that only matching functions are passed. For example if the PrintInt function accepted a different set of parameters an error would be generated.

Binding Delegates to an Object Reference

In the above example, the function called is a simple global scope function. Delegates however can reference a function on a specific object instance. In this case, Prefix binds the object reference so that it's passed to the function correctly when it's invoked:

{{C#}}
extern dynamic Console;

class MyClass
{
 public int Value;
   public void DoSomething()
   {
       Console.WriteLine("MyClass.DoSomething - " + Value.toString());
 }
}

delegate void del();

var inst = new MyClass();
inst.Value=23;

del fn = inst.DoSomething;
fn();
{{JavaScript}}
(function() {

// Prefix runtime library
var $dex = {};
$dex.bind=function(a,b){var c=a[b];return function(){c.apply(a,arguments)}} 

// class MyClass
var MyClass = function() {};
MyClass.typeName = 'MyClass';
MyClass.prototype.Value = 0;

MyClass.prototype.DoSomething = function()
{
  Console.WriteLine("MyClass.DoSomething - " + this.Value.toString());
};

// Start Global Code
var inst=new MyClass();
inst.Value = 23;
var fn=$dex.bind(inst, "DoSomething");
fn();

})();

Note that calling the delegate variable fn knows nothing of the MyClass instance, yet calling it still works thanks to the binding done by the helper function $dex.bind.

Generic Delegates

Prefix also supports generic delegates. Consider this example which is identical to the previous example except that the delegate is declared as a generic. The int parameter type isn't resolved until the delegate is used:

{{C#}}
extern dynamic Console;

class MyClass
{
 public int Value;
   public void DoSomething(int param)
  {
       Console.WriteLine("MyClass.DoSomething - " + Value.toString() + " - " + param.toString());
  }
}

delegate void del<T>(T a);

var inst = new MyClass();
inst.Value=23;

del<int> fn = inst.DoSomething;
fn(5);
{{JavaScript}}
(function() {

// Prefix runtime library
var $dex = {};
$dex.bind=function(a,b){var c=a[b];return function(){c.apply(a,arguments)}} 

// class MyClass
var MyClass = function() {};
MyClass.typeName = 'MyClass';
MyClass.prototype.Value = 0;

MyClass.prototype.DoSomething = function(param)
{
 Console.WriteLine("MyClass.DoSomething - " + this.Value.toString() + " - " + param.toString());
};

// Start Global Code
var inst=new MyClass();
inst.Value = 23;
var fn=$dex.bind(inst, "DoSomething");
fn(5);

})();

Func and Action

Now that Prefix has support for generic delegates, support for Func and Action are simply a matter of declaring them:

{{C#}}
delegate TResult Func<TResult>();
delegate TResult Func<T1, TResult>(T1 p1);
delegate TResult Func<T1, T2, TResult>(T1 p1, T2 p2);
// etc...

delegate void Action();
delegate void Action<T1>(T1 p1);
delegate void Action<T1, T2>(T1 p1, T2 p2);
// etc...

Prefix includes built-in declarations for Func and Action with up to 8 parameters:

{{C#}}
extern dynamic Console;


int addem(int a, int b)
{
   return a+b;
}

Func<int, int, int> op = addem;

Console.WriteLine(op(10, 20).toString());
{{JavaScript}}
(function() {

function addem(a, b)
{
    return a + b;
};

// Start Global Code
var op=addem;
Console.WriteLine(op(10, 20).toString());

})();

Type Inference with Delegates

Prefix supports generic type inference with delegates. The following example declares a generic function with two generic parameters TIn and TOut. Note that when the function is called, it's simply called fn and the type are inferred from the usage. ie: Prefix has inferred that TIn is int and TOut is string.

{{C#}}
extern dynamic Console;

TOut fn<TIn, TOut>(Func<TIn,TOut> cb, TIn input)
{
  return cb(input);
}

string convert(int val)
{  
    return val.toString();
}

Console.WriteLine(fn(convert, 23));
{{JavaScript}}
(function() {

function fn$2(cb, input)
{
    return cb(input);
};

function convert(val)
{
   return val.toString();
};

// Start Global Code
Console.WriteLine(fn$2(convert, 23));

})();

Conclusion

Delegates are an important concept for Prefix as they bring the language considerably closer to JavaScript's capabilities and also lead into Lambda's and Anonymous Functions which I'm working on now.

« Older - Porting 'Burst Fish' to Prefix Newer - Prefix - Arrays »