Dart 强制在泛型类型中强制子类型 (不允许基本类型) VS switch(Foo)

huangapple go评论55阅读模式
英文:

dart enforce subtype in generic type (disallow base type) VS switch(Foo<T>)

问题

以下是您要翻译的代码部分:

sealed class Base { /* ... */ }

class X extends Base { /* ... */ }
class Y extends Base { /* ... */ }

class Foo<T extends Base> {
   final T t;
   const Foo({required this.t});
}

阅读文档似乎不可能限制Foo<T>实例的类型,以便T必须是Base的真正子类型,不能是Base。这是否可能?

背景是我想要在Foo<T>实例上进行切换:

final a = Foo<X>(/* ... */);

final result = switch (a) {
  Foo<X> _ => 'x',
  Foo<Y> _ => 'y',
  Foo<Base> _ => throw 'does not exist'; // <--
}

missing_enum_constant_in_switch规则要求我指定Base _情况。这很糟糕,因为它匹配所有情况,所以如果我们以后添加一个新的class Z extends Base {/* ... */},该规则将不再报告缺少情况,而只是使用Foo<Base>情况并抛出does not exist

考虑到Basesealed,因此它也是abstract的,因此不能被实例化,因此我希望编译器不要求这种情况 - 但我理解它确实需要,因为一般来说类型限制允许Foo<Base>

作为一种解决方法,我目前正在切换a.t

final a = Foo<X>(/* ... */);

final result = switch (a.t) {
  X _ => 'x',
  Y _ => 'y',
  // 不需要`Base`情况。
}

这在示例中看起来不错,但在实际生产代码中非常繁琐且容易出错,因为如果下游函数依赖于a而不是a.t,则需要手动进行未检查的强制类型转换:

final a = Foo<X>(/* ... */);

final result = switch (a.t) {
  // 这是不可能的
  X _ => somethingX(a as Foo<X>),
  Y _ => somethingY(a as Foo<Y>),
}

这不会起作用,因为a的类型是Foo<Base>,因此Flutter会抛出type 'Foo<Base>' is not a subtype of type 'Foo<X>' in type cast

因此,我们必须进一步解决问题,通过像这样更改somethingXsomethingY以接受下转换的值:

final a = Foo<X>(/* ... */);
final t = a.t;
final result = switch (t) {
  X _ => somethingX(a, t),
  Y _ => somethingY(a, t),
}

或者通过执行类似以下操作:

final a = Foo<X>(/* ... */);
final t = a.t;
final result = switch (t) {
  X _ => somethingX(a.cast<X>()),
  Y _ => somethingY(a.cast<Y>()),
}

其中cast是:

Foo<T extends Base> {
  // ...

  Foo<T2> cast<T2 extends Base>() => Foo(
    t: t as T2,
  );
}
英文:

Given such classes

sealed class Base { /* ... */ }

class X extends Base { /* ... */ }
class Y extends Base { /* ... */ }

class Foo&lt;T extends Base&gt; {
   final T t;
   const Foo({required this.t});
}

Reading the docs it seems impossible to restrict the type of an instance of Foo&lt;T&gt; so that T must be a real sub types of Base and may not be Base. Is it possible?

Background is that I want to switch over a Foo&lt;T&gt; instance:

final a = Foo&lt;X&gt;(/* ... */);

final result = switch (a) {
  Foo&lt;X&gt; _ =&gt; &#39;x&#39;,
  Foo&lt;Y&gt; _ =&gt; &#39;y&#39;,
  Foo&lt;Base&gt; _ =&gt; throw &#39;does not exist&#39;; // &lt;--
}

The missing_enum_constant_in_switch rule requires me to specify the Base _ case. That is bad, because it matches all cases, so that if we later add a new class Z extends Base {/* ... */}, the rule would no longer report a missing case but just use the Foo&lt;Base&gt; case and throw does not exist.

Given that Base is sealed it also is abstract and hence cannot be instantiated and hence I would expect the compiler to not require that case - but I understand that it does, because in general the type restriction allows for Foo&lt;Base&gt;.

As a workaround I am currently switching over a.t.

final a = Foo&lt;X&gt;(/* ... */);

final result = switch (a.t) {
  X _ =&gt; &#39;x&#39;,
  Y _ =&gt; &#39;y&#39;,
  // no `Base` case is required.
}

This looks fine in an example, but is very cumbersome and error prone in realistic production code, because it would require a manual unchecked cast of a if downstream functions depend on a rather than on a.t:

final a = Foo&lt;X&gt;(/* ... */);

final result = switch (a.t) {
  // THIS IS NOT POSSIBLE
  X _ =&gt; somethingX(a as Foo&lt;X&gt;),
  Y _ =&gt; somethingY(a as Foo&lt;Y&gt;),
}

This would not work, because a is of type Foo&lt;Base&gt; and hence flutter throws type &#39;Foo&lt;Base&gt;&#39; is not a subtype of type &#39;Foo&lt;X&gt;&#39; in type cast.

Hence, we further must workaround by changing somethingX and somethingY to also accept the downcasted value like this:

final a = Foo&lt;X&gt;(/* ... */);
final t = a.t;
final result = switch (t) {
  X _ =&gt; somethingX(a, t),
  Y _ =&gt; somethingY(a, t),
}

or by doing something like:

final a = Foo&lt;X&gt;(/* ... */);
final t = a.t;
final result = switch (t) {
  X _ =&gt; somethingX(a.cast&lt;X&gt;()),
  Y _ =&gt; somethingY(a.cast&lt;Y&gt;()),
}

where cast is:

Foo&lt;T extends Base&gt; {
  // ...

  Foo&lt;T2&gt; cast&lt;T2 extends Base&gt;() =&gt; Foo(
    t: t as T2,
  );
}

答案1

得分: 1

这是不可能的。

Dart类型参数仅受子类型检查的限制,因此没有办法同时接受XY,并且不接受Base,根据当前的定义是无法做到的。

您可能会尝试的一个方法,实际上是行不通的,那就是有一个隐藏的共享超类:

class Base {}
class _SecretBase extends Base {}
class X extends _SecretBase {}
class Y extends _SecretBase {}

class Foo<T extends _SecereteBase> { ... }

然后您可能会认为,在您的库之外的某个地方,不会能够创建Foo<_SecretBase>,并且他们不能使用Base,因此必须使用Base的适当子类型之一。
不幸的是,这不起作用,因为像new Foo()这样的原始类型将会"实例化到边界"并创建一个Foo<_SecretBase>,即使调用者无法编写类型。

因此,无法将类型参数限制为共享超类型的适当子类型。

英文:

It's not possible.

Dart type parameters are only restricted by subtype checks, so there is no way to accept both X and Y and also not accept Base, not with the current definitions.

The one hack you'd probably try, and which won't actually work, is to have a hidden shared superclass:

class Base {}
class _SecretBase extends Base {}
class X extends _SecretBase {}
class Y extends _SecretBase {}

class Foo&lt;T extends _SecereteBase&gt; { ... }

Then you'd think that someone outside of your library would not be able to create a Foo&lt;_SecretBase&gt;, and they can't use Base, so would have to use one of the proper subtypes of Base.
Sadly it doesn't work, since a raw type like new Foo() would "instantiate to bounds" and create a Foo&lt;_SecretBase&gt;, even if the caller couldn't write the type.

So, it's not possible to restrict type arguments to proper subtypes of a shared supertype.

huangapple
  • 本文由 发表于 2023年6月30日 02:05:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/76583585.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定