Reputation: 595
What if I have classes that are different only by some constant used in code. Is it possible to have one generic implementation without runtime cost?
Here is the example (it's a little bit too long...)
@:enum abstract Param(Int) {
var foo = 0;
var bar = 1;
}
class WorkBase {
public function new() {}
private inline function work_impl(p: Param): Void {
if(p == foo) {
trace('foo');
}
else {
trace('bar');
}
}
public function work(): Void {
}
}
class WorkFoo extends WorkBase{
override public function work(): Void {
work_impl(foo);
}
}
class WorkBar extends WorkBase {
override public function work(): Void {
work_impl(bar);
}
}
class Test {
public static function main() {
var workFoo = new WorkFoo();
var workBar = new WorkBar();
workFoo.work();
workBar.work();
}
}
After compilation with -D analyzer-optimize
we will see that WorkFoo.work()
and WorkBar.work()
functions were optimized and contain only one branch of code that matches one of the Param
values. In real life there are lot of such comparisons in work_impl()
, and they all are optimized out. That's good.
But what if I do not want to create WorkFoo
and WorkBar
by hand. Is it possible to do something like this:
@:generic
class WorkBase<PARAM> {
private inline function work_impl(p: Param): Void {
...
}
public function work(): Void {
work_impl(PARAM);
}
}
The closest thing I know is const-type-parameter. But I do not feel generic build is a good choice here.
Upvotes: 3
Views: 215
Reputation: 34138
The closest thing I know is const-type-parameter. But I do not feel generic build is a good choice here.
Const type parameters can be used without @:genericBuild
- a const type parameter in combination with @:generic
is enough to get the desired optimization:
@:enum abstract Param(Int) from Int {
var foo = 0;
var bar = 1;
}
@:generic class Work<@:const PARAM:Int> {
public function new() {}
public function work():Void {
if (PARAM == foo) {
trace('foo');
} else {
trace('bar');
}
}
}
class Main {
public static function main() {
var workFoo = new Work<0>();
var workBar = new Work<1>();
workFoo.work();
workBar.work();
}
}
Due to @:generic
, one class is generated for each constant value, for instance on JS the output looks like this:
var Work_$0 = function() {
};
Work_$0.prototype = {
work: function() {
console.log("source/Main.hx:11:","foo");
}
};
var Work_$1 = function() {
};
Work_$1.prototype = {
work: function() {
console.log("source/Main.hx:13:","bar");
}
};
Note that this example fails with a "constraint check failure" in Haxe 3.4.7 for some reason, but works fine with Haxe 4 preview 4 and later. Another limitation is that neither new Work<Param.foo>()
nor new Work<foo>()
work - you need to pass the actual constant value.
Upvotes: 3