是否可以对 class 字段和属性使用 Haxe 类型约束?
Is it possible to use Haxe type constraints with both class fields and properties?
我有一个带有约束类型参数的方法,我希望能够在具有 x
和 y
类型 Float
:[=19= 的任何对象上使用该方法]
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
}
问题在于,当提供的 T
上的 x
and/or y
是具有 getter 或 setter 的属性时,类型约束检查会失败,从而导致错误喜欢:
Constraint check failure for nearest.T
Inconsistent access for field x : (get,set) should be (default,default)
我认为将 class 字段和属性视为与编写通用算法相同的做法会很好。如果这对于类型约束是不可能的,那么有没有办法用 Haxe 3.2 来完成?
这是不可能的,因为 Haxe getter / setters 是在编译时解析的。
这意味着编译器将属性查找 (p.x
) 替换为适当的 getter 调用 (p.get_x()
),这意味着类型必须包含 x
和 y
是 (get,set)
。
要告诉编译器您的 T
类型需要这样的 x
和 y
,您可以创建一个 typedef,例如:
typedef PointProps = {
var x(get,set): Float;
var y(get,set): Float;
}
然后使用<T:PointProps>
那么显然您的方法将不再适用于简单变量。
即使使用 abstract
s,似乎也不可能有一个方法同时兼容变量和属性。
扩展@clemos 的回答 – 无法像使用简单的 typedef 一样对待 class 字段和属性,因为 Haxe getter和设置器在编译时解析——有另一种方法来处理这个问题。
然而,这有点麻烦。你可以看到它的实际效果 here.
首先,我们在可同时处理变量和属性的包装器上构建抽象:
// 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;
}
现在我们在原始 nearest
方法的简化 Point->Point
版本中使用此摘要并对其进行测试。
// 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
}
}
注意:我觉得这个解决方案可以很容易地改进(@:from
两种实现方式是一样的),但是现在凌晨3点,没有想到其他的。如果我最终想出如何简化它,我会回来编辑它。
我有一个带有约束类型参数的方法,我希望能够在具有 x
和 y
类型 Float
:[=19= 的任何对象上使用该方法]
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
}
问题在于,当提供的 T
上的 x
and/or y
是具有 getter 或 setter 的属性时,类型约束检查会失败,从而导致错误喜欢:
Constraint check failure for nearest.T
Inconsistent access for field x : (get,set) should be (default,default)
我认为将 class 字段和属性视为与编写通用算法相同的做法会很好。如果这对于类型约束是不可能的,那么有没有办法用 Haxe 3.2 来完成?
这是不可能的,因为 Haxe getter / setters 是在编译时解析的。
这意味着编译器将属性查找 (p.x
) 替换为适当的 getter 调用 (p.get_x()
),这意味着类型必须包含 x
和 y
是 (get,set)
。
要告诉编译器您的 T
类型需要这样的 x
和 y
,您可以创建一个 typedef,例如:
typedef PointProps = {
var x(get,set): Float;
var y(get,set): Float;
}
然后使用<T:PointProps>
那么显然您的方法将不再适用于简单变量。
即使使用 abstract
s,似乎也不可能有一个方法同时兼容变量和属性。
扩展@clemos 的回答 – 无法像使用简单的 typedef 一样对待 class 字段和属性,因为 Haxe getter和设置器在编译时解析——有另一种方法来处理这个问题。
然而,这有点麻烦。你可以看到它的实际效果 here.
首先,我们在可同时处理变量和属性的包装器上构建抽象:
// 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;
}
现在我们在原始 nearest
方法的简化 Point->Point
版本中使用此摘要并对其进行测试。
// 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
}
}
注意:我觉得这个解决方案可以很容易地改进(@:from
两种实现方式是一样的),但是现在凌晨3点,没有想到其他的。如果我最终想出如何简化它,我会回来编辑它。