Reputation: 1133
I have a method with a constrained type parameter that I want to be able to use on any object with an x
and y
of type Float
:
public static function nearest<T:{var x:Float; var y:Float;}>
(x1:Float, y1:Float, objs:Array<T>, distanceMetric:Float->Float->Float->Float->Float):T {
// Snip. Returns the object nearest to x1,y1
}
The problem with this is that the type constraint check fails when the x
and/or y
on the supplied T
are properties with getters or setters, causing errors like:
Constraint check failure for nearest.T
Inconsistent access for field x : (get,set) should be (default,default)
I think it would be neat to treat class fields and properties like they are the same for writing generic algorithms. If this is not possible with type constraints, then is there a way it can be done with Haxe 3.2?
Upvotes: 2
Views: 570
Reputation: 1557
Extending on @clemos' answer – that it isn't possible to treat class fields and properties like they are the same with a simple typedef, since Haxe getter and setters are resolved at compile time – there is an alternative way to handle this.
It is, however, somewhat cumbersome. You can see it in action here.
First, we build an abstract over a wrapper that works with both variables and properties:
// let's start with our possible point types
typedef NoGetters = { x:Float, y:Float };
typedef WithProperties = { var x(get,set):Float; var y(get,set):Float; };
// now, let's prepare a common implementation for them
typedef SomePoint2Impl<T> = { obj:T, get_x:Void->Float, get_y:Void->Float };
// and use it in and abstract
abstract SomePoint2<T>(SomePoint2Impl<T>) from SomePoint2Impl<T> {
// these wrap points in the common type
@:from static function fromNoGetters<T:NoGetters>(p:T):SomePoint2<T>
return { obj : p, get_x : function () return p.x, get_y : function () return p.y };
@:from static function fromWithProperties<T:WithProperties>(p:T):SomePoint2<T>
return { obj : p, get_x : function () return p.x, get_y : function () return p.y };
// and this restores the original type from the common one
@:to function toOriginal():T
return this.obj;
}
Now we use this abstract in a simplified Point->Point
version of your original nearest
method and test it.
// a test class for points with properties
// (points without properties can be tested with anonymous structs)
// don't use @:isVar, so that is clear that the getter was called
class TestPoint {
var _x:Float;
var _y:Float;
public var x(get,set):Float;
function get_x() return _x;
function set_x(x) return _x = x;
public var y(get,set):Float;
function get_y() return _y;
function set_y(y) return _y = y;
public function toString()
return '(x:$x, y:$y)';
public function new(x,y)
{
_x = x;
_y = y;
}
}
class Test {
// a simplified function that takes some "point" and returns it back
// it retains the basic type system problem as `nearest`
public static function test<T>(p:SomePoint2<T>)
return p;
static function main()
{
// some points
var p1 = { x:1., y:2. };
var p2 = new TestPoint(1, 2);
// calls to test
var t1 = test(p1);
var t2 = test(p2);
$type(t1);
$type(t2);
// show that identity has been preserved
// t1,t2 both get cast back to their original types
trace(t1 == p1);
trace(t2 == p2);
// show explicit conversions
trace((t1:{x:Float, y:Float}));
trace((t2:TestPoint));
// trace((t1:TestPoint)); // fails as expected: SomePoint2<{ y : Float, x : Float }> should be TestPoint
}
}
Note: I feel that this solution can be easily improved (both @:from
implementations are equal), but it's 3 am and nothing else comes to mind. If I eventually figure out how to simplify this, I'll come back and edit this.
Upvotes: 4
Reputation: 426
It is not possible, because Haxe getter / setters are resolved at compile time.
This means that the compiler replaces the properties lookup (p.x
) with the appropriate getter call (p.get_x()
), which means the type has to contain the info that x
and y
are (get,set)
.
To tell the compiler your T
type needs to have such x
and y
, you can create a typedef like :
typedef PointProps = {
var x(get,set): Float;
var y(get,set): Float;
}
And then use <T:PointProps>
Then obviously your method won't work with simple variables anymore.
It doesn't seem possible to have a single method compatible with both variables and properties, even using abstract
s.
Upvotes: 1