使用不变类型参数处理 case 类 上的提取器
dealing with extractors on case classes with an invariant type parameter
我认为,我的问题最好用一些示例代码来描述:
class Foo[T]
class Bar extends Foo[String]
class Baz extends Foo[Int]
trait X { def f: Foo[_] }
case class Wrapper[D](f: Foo[D]) extends X
val w: X = Wrapper(new Bar)
w match { case Wrapper(_: Bar) => 1 }
最后一行失败
found : Bar
required: Foo[Any]
Note: String <: Any (and Bar <: Foo[String]), but class Foo is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
我知道发生这种情况是因为 unapply
是用类型参数定义的,它被推断为 Any
,因此它抱怨 String
不兼容。
但问题是有什么方法可以让它发挥作用吗?我试着给抽取器一个类型参数,像这样:w match { case Wrapper[String](_: Bar) => 1 }
,但它说它不接受参数(这是一个谎言)... :(
到目前为止我想出的唯一方法就是这个丑宝宝:
w match { case w: Wrapper[String] if w.f.isInstanceOf[Bar] => 1 }
或者,也许,
Option(w).map(_.f) match { case Some(_: Bar) => 1 }
(后者有效,因为 Option
是协变的,但不幸的是我无法使 类 协变)。此外,如果没有一些额外的丑陋 IRL,我不能真正使用最后一个替代方案,因为现实生活中 X
的等价物实际上没有 f
.
有更好的主意吗?
定义自定义提取器
Wrapper.unapply
确实采用类型参数,但您不能在模式匹配序列中指定一个*,因此编译器会为您推断一个(如果编译器这样做,通常是 Any
或 Nothing
).
而且,实际上,您不希望它这样做,因为当您强制元素键入 X
时,您正在删除类型信息。所以,你想要一个存在版本的匹配器
object WrapperEx {
def unapply(w: Wrapper[_]): Option[Foo[_]] = Wrapper.unapply(w)
}
并像这样使用它:
w match { case WrapperEx(_: Bar) => 1 }
可运行版本here
- 好消息:您可以委托给生成的案例 class 匹配器。
- 坏消息:你不能在 case class companion 中定义它。 Scala 已经很高兴地选错了,所以它无法消除歧义。
不过,我想说还不错
* 你可以在最新的 Typelevel Scala 中使用,但我不确定它如何与类型转换一起工作,我无法让它为你的情况工作。
您可以参数化 trait X
以摆脱 def f: Foo[_]
中的存在类型,我认为这就是使编译器出错的原因。以下代码有效:
class Foo[T]
class Bar extends Foo[String]
class Baz extends Foo[Int]
trait X[A] { def f: Foo[A] }
case class Wrapper[D](f: Foo[D]) extends X[D]
val w: X[String] = Wrapper(new Bar) // type ascription can be omitted and will be inferred
w match { case Wrapper(_: Bar) => 1 }
我认为,我的问题最好用一些示例代码来描述:
class Foo[T]
class Bar extends Foo[String]
class Baz extends Foo[Int]
trait X { def f: Foo[_] }
case class Wrapper[D](f: Foo[D]) extends X
val w: X = Wrapper(new Bar)
w match { case Wrapper(_: Bar) => 1 }
最后一行失败
found : Bar
required: Foo[Any]
Note: String <: Any (and Bar <: Foo[String]), but class Foo is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
我知道发生这种情况是因为 unapply
是用类型参数定义的,它被推断为 Any
,因此它抱怨 String
不兼容。
但问题是有什么方法可以让它发挥作用吗?我试着给抽取器一个类型参数,像这样:w match { case Wrapper[String](_: Bar) => 1 }
,但它说它不接受参数(这是一个谎言)... :(
到目前为止我想出的唯一方法就是这个丑宝宝:
w match { case w: Wrapper[String] if w.f.isInstanceOf[Bar] => 1 }
或者,也许,
Option(w).map(_.f) match { case Some(_: Bar) => 1 }
(后者有效,因为 Option
是协变的,但不幸的是我无法使 类 协变)。此外,如果没有一些额外的丑陋 IRL,我不能真正使用最后一个替代方案,因为现实生活中 X
的等价物实际上没有 f
.
有更好的主意吗?
定义自定义提取器
Wrapper.unapply
确实采用类型参数,但您不能在模式匹配序列中指定一个*,因此编译器会为您推断一个(如果编译器这样做,通常是 Any
或 Nothing
).
而且,实际上,您不希望它这样做,因为当您强制元素键入 X
时,您正在删除类型信息。所以,你想要一个存在版本的匹配器
object WrapperEx {
def unapply(w: Wrapper[_]): Option[Foo[_]] = Wrapper.unapply(w)
}
并像这样使用它:
w match { case WrapperEx(_: Bar) => 1 }
可运行版本here
- 好消息:您可以委托给生成的案例 class 匹配器。
- 坏消息:你不能在 case class companion 中定义它。 Scala 已经很高兴地选错了,所以它无法消除歧义。
不过,我想说还不错
* 你可以在最新的 Typelevel Scala 中使用,但我不确定它如何与类型转换一起工作,我无法让它为你的情况工作。
您可以参数化 trait X
以摆脱 def f: Foo[_]
中的存在类型,我认为这就是使编译器出错的原因。以下代码有效:
class Foo[T]
class Bar extends Foo[String]
class Baz extends Foo[Int]
trait X[A] { def f: Foo[A] }
case class Wrapper[D](f: Foo[D]) extends X[D]
val w: X[String] = Wrapper(new Bar) // type ascription can be omitted and will be inferred
w match { case Wrapper(_: Bar) => 1 }