Hacklang - 为什么受保护的成员不变?
Hacklang - Why are protected members invariant?
public个会员案例
通过全权访问调用作用域,public 成员不变也就不足为奇了:
<?hh // strict
class Foo<+T> {
public function __construct(
public T $v
) {}
}
class ViolateType {
public static function violate(Foo<int> $foo): void {
self::cast_and_set($foo);
echo $foo->v + 1; // string + integer
}
public static function cast_and_set(Foo<arraykey> $foo): void {
$foo->v = "Poof! The integer `violate()` expects is now a string.";
}
}
// call ViolateType::foo(new Foo(1)); and watch the fireworks
这里的问题是violate
和cast_and_set
都可以读取和修改相同的值(Foo->v
),但期望不同它的类型。
但是,受保护的成员似乎不存在此问题。
试图为受保护的成员创建违规
由于 private
和 protected
之间的唯一区别是对后代的可见性,让我们以 class (ImplCov
) 为例,在一定数量的受保护成员之外, 否则在类型上有效协变,并将其扩展为 class (ImplInv
) 在该类型上不变。值得注意的是,在 T
上保持不变允许我公开 public setter — violate(T $v): T
— 我将在其中尝试打破类型。
<?hh // strict
// helper class hierarchy
class Base {}
class Derived extends Base {}
class ImplCov<+T> {
public function __construct(
protected T $v
) {}
}
class ImplInv<T> extends ImplCov<T> {
public function violate(T $v): T {
// Try to break types here
}
}
对于 ImplInv<Derived>
的实例,我不得不转换为 ImplCov<Derived>
,然后利用协方差转换为 ImplCov<Base>
。这就像最危险的事情一样,所有三种类型都指代同一个对象。让我们检查一下每种类型之间的关系:
ImplInv<Derived>
和 ImplCov<Base>
:public 成员案例中的违规发生在 属性 更改为超类型(int-> arraykey) 或具有公共超类型 (int->string) 的不相交类型。但是,因为 ImplCov<Base>
在 T
上是协变的,所以不存在可以传递 Base
实例并使 v
为真 Base
的方法。 ImplCov
的方法也无法生成 new Base()
并将其分配给 v
,因为它不知道 T
.1[= 的最终类型95=]
同时,因为转换 ImplCov<Derived> --> ImplCov<Base> --> ...
只会导致它的派生性降低,所以 ImplInv::violate(T)
保证在最坏的情况下将 v
设置为最终 ImplCov
的子类型的 T
,保证对最终 T
的有效转换。 ImplInv<Derived>
的 Derived 无法强制转换,因此一旦参数化,该类型就一成不变。
ImplInv<Derived>
和 ImplCov<Derived>
:它们可以共存,因为 T
它们之间是相同的,转换只是最外层的类型。
ImplCov<Derived>
和 ImplCov<Base>
:它们可以共存,前提是 ImplCov
是有效协变的。 v
的 protected
可见性与 private
没有区别,因为它们是相同的 class.
所有这些似乎都表明受保护的可见性对于协变类型来说是合乎规范的。我错过了什么吗?
1.我们实际上可以通过引入 super
约束来生成 new Base()
:ImplCov<T super Base>
,但这更弱,因为根据定义 ImplInv
必须在 extends
语句与超类型,使 ImplInv
与 v
的操作安全。另外,ImplCov
不能对 T
.
的成员做任何假设
请记住,subclass 可以修改 class 的 任何 对象的 protected
成员,而不仅仅是 $this
.因此,对上面的示例稍作修改,使用 protected
成员来类似地破坏类型系统——我们所要做的就是使 ViolateType
成为 Foo
的子 class(我们将 T
设置为什么,或者我们是否使 ViolateType
通用或其他什么都不重要)。
<?hh // strict
class Foo<+T> {
public function __construct(
/* HH_IGNORE_ERROR[4120] */
protected T $v
) {}
}
class ViolateType extends Foo<void> {
public static function violate(Foo<int> $foo): void {
self::cast_and_set($foo);
echo $foo->v + 1; // string + integer
}
public static function cast_and_set(Foo<arraykey> $foo): void {
$foo->v = "Poof! The integer `violate()` expects is now a string.";
}
}
这通过了类型检查器,只对受保护成员进行了一次错误抑制——因此允许受保护成员协变会破坏类型系统。
public个会员案例
通过全权访问调用作用域,public 成员不变也就不足为奇了:
<?hh // strict
class Foo<+T> {
public function __construct(
public T $v
) {}
}
class ViolateType {
public static function violate(Foo<int> $foo): void {
self::cast_and_set($foo);
echo $foo->v + 1; // string + integer
}
public static function cast_and_set(Foo<arraykey> $foo): void {
$foo->v = "Poof! The integer `violate()` expects is now a string.";
}
}
// call ViolateType::foo(new Foo(1)); and watch the fireworks
这里的问题是violate
和cast_and_set
都可以读取和修改相同的值(Foo->v
),但期望不同它的类型。
但是,受保护的成员似乎不存在此问题。
试图为受保护的成员创建违规
由于 private
和 protected
之间的唯一区别是对后代的可见性,让我们以 class (ImplCov
) 为例,在一定数量的受保护成员之外, 否则在类型上有效协变,并将其扩展为 class (ImplInv
) 在该类型上不变。值得注意的是,在 T
上保持不变允许我公开 public setter — violate(T $v): T
— 我将在其中尝试打破类型。
<?hh // strict
// helper class hierarchy
class Base {}
class Derived extends Base {}
class ImplCov<+T> {
public function __construct(
protected T $v
) {}
}
class ImplInv<T> extends ImplCov<T> {
public function violate(T $v): T {
// Try to break types here
}
}
对于 ImplInv<Derived>
的实例,我不得不转换为 ImplCov<Derived>
,然后利用协方差转换为 ImplCov<Base>
。这就像最危险的事情一样,所有三种类型都指代同一个对象。让我们检查一下每种类型之间的关系:
ImplInv<Derived>
和ImplCov<Base>
:public 成员案例中的违规发生在 属性 更改为超类型(int-> arraykey) 或具有公共超类型 (int->string) 的不相交类型。但是,因为ImplCov<Base>
在T
上是协变的,所以不存在可以传递Base
实例并使v
为真Base
的方法。ImplCov
的方法也无法生成new Base()
并将其分配给v
,因为它不知道T
.1[= 的最终类型95=]同时,因为转换
ImplCov<Derived> --> ImplCov<Base> --> ...
只会导致它的派生性降低,所以ImplInv::violate(T)
保证在最坏的情况下将v
设置为最终ImplCov
的子类型的T
,保证对最终T
的有效转换。ImplInv<Derived>
的 Derived 无法强制转换,因此一旦参数化,该类型就一成不变。ImplInv<Derived>
和ImplCov<Derived>
:它们可以共存,因为T
它们之间是相同的,转换只是最外层的类型。ImplCov<Derived>
和ImplCov<Base>
:它们可以共存,前提是ImplCov
是有效协变的。v
的protected
可见性与private
没有区别,因为它们是相同的 class.
所有这些似乎都表明受保护的可见性对于协变类型来说是合乎规范的。我错过了什么吗?
1.我们实际上可以通过引入 super
约束来生成 new Base()
:ImplCov<T super Base>
,但这更弱,因为根据定义 ImplInv
必须在 extends
语句与超类型,使 ImplInv
与 v
的操作安全。另外,ImplCov
不能对 T
.
请记住,subclass 可以修改 class 的 任何 对象的 protected
成员,而不仅仅是 $this
.因此,对上面的示例稍作修改,使用 protected
成员来类似地破坏类型系统——我们所要做的就是使 ViolateType
成为 Foo
的子 class(我们将 T
设置为什么,或者我们是否使 ViolateType
通用或其他什么都不重要)。
<?hh // strict
class Foo<+T> {
public function __construct(
/* HH_IGNORE_ERROR[4120] */
protected T $v
) {}
}
class ViolateType extends Foo<void> {
public static function violate(Foo<int> $foo): void {
self::cast_and_set($foo);
echo $foo->v + 1; // string + integer
}
public static function cast_and_set(Foo<arraykey> $foo): void {
$foo->v = "Poof! The integer `violate()` expects is now a string.";
}
}
这通过了类型检查器,只对受保护成员进行了一次错误抑制——因此允许受保护成员协变会破坏类型系统。