Function types becoming dynamic after compilation

Hi All,

Apologies for the third post in a row. I wanted to highlight the speed increases I am getting on JavaScript and C++ and would like opinions as to why this might be the case.

I’ve uploaded the latest version of the code here (which now supports variable capture): https://lib.haxe.org/p/haxe-delegates/0.0.1/

Hxcpp:

This is without debugging:

src/Test.hx:17: *** Running without inlines ***
src/Test.hx:53: Haxe function type: 0.3874378
src/Test.hx:59: Delegate: 0.0279917
src/Test.hx:24: *** Running with inlines ***
src/Test.hx:53: Haxe function type: 0.3732177
src/Test.hx:59: Delegate: 0.0155093000000001
src/Test.hx:31: *** Running with anonymous functions ***
src/Test.hx:53: Haxe function type: 0.3604134
src/Test.hx:59: Delegate: 0.0193384999999999

In other words, the Delegate type executes 24 times faster and 14 times faster with and without inlines respectively.

JavaScript:

app.js:24 src/Test.hx:17: *** Running without inlines ***
app.js:60 src/Test.hx:53: Haxe function type: 0.008699999999254943
app.js:68 src/Test.hx:59: Delegate: 0.007800000000745058
app.js:30 src/Test.hx:24: *** Running with inlines ***
app.js:60 src/Test.hx:53: Haxe function type: 0.09140000000037253
app.js:68 src/Test.hx:59: Delegate: 0.012100000001490113
app.js:37 src/Test.hx:31: *** Running with anonymous functions ***
app.js:60 src/Test.hx:53: Haxe function type: 0.06580000000074504
app.js:68 src/Test.hx:59: Delegate: 0.014500000000000013

Here, the Delegate type executes in roughly the same time as a function type, but 8 times faster with inlines and 4 times faster with anonymous functions.

Some obvious limitations still exist:

  1. Oddly enough, anonymous functions break if we try to reference imported types that expect type parameters, and so need to be preceded by their package in order to work.
  2. Larger output size, and so balancing the cost of runtime execution and output size as expected from generics should be considered.
  3. Delegate types cannot be generated from functions or classes with type parameters.

Hello,

nice, thank you for doing this. I tried to use vector of delegates like this

delegates[0] = DelegateBuilder.from((a, b) -> (return a+b+outer+v));

but fails throwing Uncaught exception Empty module name is not allowed.

I ended up using

	testDelegate = DelegateBuilder.from((a, b) -> (return a+b+outer+v));
	delegates[0] = testDelegate;

Is there a way to overcome this limitation?

Aye, there will definitely be some weird things with the anonymous functions for the time being. I’ll look into this :slight_smile:

Out of curiosity, does it fail if you build with

delegates[0] = DelegateBuilder.from(myFunction);

where myFunction is a class function?

Yes,it does fails. I moved delegates to the constructor, but it fails in any other function (init, setup, etc)

public function new() {
    outer = 5;
    var v = 3;
    delegates = new Vector(3);
    testFunc = (a, b) -> (return a+b+outer+v);
    testDelegate = DelegateBuilder.from((a, b) -> (return a+b+outer+v));
    delegates[0] = testDelegate;
    testFunc = myFunction;
    testDelegate = DelegateBuilder.from(myFunction);
    delegates[1] = testDelegate;
    testFunc = myInlinedFunction;
    testDelegate = DelegateBuilder.from(myInlinedFunction);
    delegates[2] = testDelegate;
}

So I have a solution. I’ll push it to the repo once I tidy things up (which it very much needs)… All delegates will have to have their argument types and return types unified explicitly, like so:

delegates[0] = DelegateBuilder.from((a : Int, b : Int) -> (return a+b+outer : Int));

but I feel like this is a small concession to make as its better than having uncaught exceptions in parts of code that should work

Can you catch exception in your macro? And then generate temporary variable used for assignment. Error is thrown because there is no object present in array/vector field (it’s null by default) I think.

The exception is thrown because the builder expects the lhs of the assignment to be of a specific type, namely a delegate abstract class which it then tries to unify against. It’s not always possible to retrieve the lhs type it seems, which is why your example was failing. It also failed for local assignments too. Not sure if catching an exception would have worked?

Explictly defining the argument types and having a return type check for (Int, Int)->Int lets us just determine if a Delegate_Int_Int_Int was generated and if so we can set it as the superclass to the delegate we generate from the function expression. This then lets us assign delegates locally or to positions in a Vector.

Yes, unification fails because there is no type present (null doesn’t unify), but in that case you can use temporary variable of delegate type for assignment. So that you support both typed and not-typed (dynamic).