如何在存在更高类型的情况下控制模式匹配中绑定变量的推断类型
How to control the inferred type for bound variables in pattern matching in the presence of higher-kinded types
(这是基于 http://bertails.org/2015/02/15/abstract-algebraic-data-type 上的文章)
首先,我正在定义 scala.Option
的抽象版本。
import scala.language.higherKinds
trait OptionSig {
type Option[+_]
type Some[+A] <: Option[A]
type None <: Option[Nothing]
}
abstract class OptionOps[Sig <: OptionSig] extends Extractors[Sig] {
def some[A](x: A): Sig#Some[A]
def none: Sig#None
def fold[A, B](opt: Sig#Option[A])(ifNone: => B, ifSome: A => B): B
}
我希望能够在 Sig#Option[A]
上使用模式匹配,所以 Extractors
看起来像这样:
trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>
object Some {
def unapply[A](opt: Sig#Option[A]): scala.Option[A] =
fold(opt)(scala.None, a => scala.Some(a))
}
object None {
def unapply[A](opt: Sig#Option[A]): Option[Unit] =
fold(opt)(scala.Some(()), _ => scala.None)
}
}
现在我可以写这个程序了:
class Program[Sig <: OptionSig](implicit ops: OptionOps[Sig]) extends App {
import ops._
val opt: Sig#Option[Int] = some(42)
opt match {
case None(_) => sys.error("")
case Some(42) => println("yay")
case Some(_) => sys.error("")
}
}
我可以用这个实现来测试它。
trait ScalaOption extends OptionSig {
type Option[+A] = scala.Option[A]
type Some[+A] = scala.Some[A]
type None = scala.None.type
}
object ScalaOption {
implicit object ops extends OptionOps[ScalaOption] {
def some[A](x: A): ScalaOption#Some[A] = scala.Some(x)
val none: ScalaOption#None = scala.None
def fold[A, B](opt: ScalaOption#Option[A])(ifNone: => B, ifSome: A => B): B =
opt match {
case scala.None => ifNone
case scala.Some(x) => ifSome(x)
}
}
}
object Main extends Program[ScalaOption]
看起来可行,但有一件烦人的事情我想不通。
和scala.Option
,Option(42) match { case s @ Some(42) => s }
中s
的类型是Some[Int]
。但是在我上面的代码片段中,它是 Sig#Option[Int]
,我想改为 Sig#Some[Int]
。
所以我尝试了以下内容以更接近 scalac 为其案例生成的内容 类:
trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>
object Some {
def unapply[A](s: Sig#Some[A]): scala.Option[A] =
fold(s)(scala.None, a => scala.Some(a))
}
object None {
def unapply(n: Sig#None): Option[Unit] =
fold(n)(scala.Some(()), (_: Any) => scala.None)
}
}
但现在我收到如下警告:
[warn] Main.scala:78: abstract type pattern Sig#None is unchecked since it is eliminated by erasure
[warn] case None(_) => sys.error("")
我不确定为什么会这样,因为 Sig#None
是 Sig#Option[Int]
的子类型,并且 在编译时已知 。
运行时也还可以,但推断的类型仍然不是我期望的类型。
所以问题是
- 尽管有子类型信息,为什么这里提到类型擦除?
- 如何在
(some(42): Sig#Option[Int]) match { case s @ Some(42) => s }
中为 s
获得 Sig#Option[Int]
不幸的是,你不能如愿以偿。问题是 scalac 知道 Sig#None <: Sig#Option[A]
是不够的,它必须能够验证它传递给 unapply
的值实际上是 Sig#None
。这适用于 scala.Option
,因为编译器可以在将类型传递给 [=12= 之前生成 instanceof
检查以验证类型实际上是 Some
还是 None
] 方法。如果检查失败,它 跳过 该模式(并且永远不会调用 unapply
)。
在你的例子中,由于 scalac 只知道 opt
是一个 Sig#Option[Int]
,它不能做任何事情来保证该值实际上是一个 Some
或 None
在将其传递给 unapply
.
之前
那么它有什么作用呢? 无论如何它都会传递值!这是什么意思?好吧,让我们稍微修改一下提取器:
trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>
object Some {
def unapply[A](s: Sig#Some[A]): scala.Option[A] =
fold(s)(scala.None, a => scala.Some(a))
}
object None {
def unapply(n: Sig#None): Option[Unit] =
scala.Some(())
}
}
我们所做的只是停止在 None
情况下使用 fold
。既然我们 知道 参数必须是 Sig#None
,为什么还要调用 fold
,对吧?我的意思是,我们不希望这里传递 Sig#Some
,对吧?
当我们 运行 这个例子时,你会点击 RuntimeException
,因为我们的第一个模式匹配 成功 并调用 ???
.在 scala.Option
的情况下,模式会失败,因为它受到生成的 instanceof
检查的保护。
我举了另一个例子来展示另一个危险,我们在 Sig#Some
中添加了一个小约束,这让我们也避免了 Some
情况下的 fold
:https://gist.github.com/tixxit/ab99b741d3f5d2668b91
无论如何,您的具体案例在技术上是安全的。我们知道您使用了 fold
,因此使用 Sig#Option
是安全的。问题是 scalac
不知道。
(这是基于 http://bertails.org/2015/02/15/abstract-algebraic-data-type 上的文章)
首先,我正在定义 scala.Option
的抽象版本。
import scala.language.higherKinds
trait OptionSig {
type Option[+_]
type Some[+A] <: Option[A]
type None <: Option[Nothing]
}
abstract class OptionOps[Sig <: OptionSig] extends Extractors[Sig] {
def some[A](x: A): Sig#Some[A]
def none: Sig#None
def fold[A, B](opt: Sig#Option[A])(ifNone: => B, ifSome: A => B): B
}
我希望能够在 Sig#Option[A]
上使用模式匹配,所以 Extractors
看起来像这样:
trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>
object Some {
def unapply[A](opt: Sig#Option[A]): scala.Option[A] =
fold(opt)(scala.None, a => scala.Some(a))
}
object None {
def unapply[A](opt: Sig#Option[A]): Option[Unit] =
fold(opt)(scala.Some(()), _ => scala.None)
}
}
现在我可以写这个程序了:
class Program[Sig <: OptionSig](implicit ops: OptionOps[Sig]) extends App {
import ops._
val opt: Sig#Option[Int] = some(42)
opt match {
case None(_) => sys.error("")
case Some(42) => println("yay")
case Some(_) => sys.error("")
}
}
我可以用这个实现来测试它。
trait ScalaOption extends OptionSig {
type Option[+A] = scala.Option[A]
type Some[+A] = scala.Some[A]
type None = scala.None.type
}
object ScalaOption {
implicit object ops extends OptionOps[ScalaOption] {
def some[A](x: A): ScalaOption#Some[A] = scala.Some(x)
val none: ScalaOption#None = scala.None
def fold[A, B](opt: ScalaOption#Option[A])(ifNone: => B, ifSome: A => B): B =
opt match {
case scala.None => ifNone
case scala.Some(x) => ifSome(x)
}
}
}
object Main extends Program[ScalaOption]
看起来可行,但有一件烦人的事情我想不通。
和scala.Option
,Option(42) match { case s @ Some(42) => s }
中s
的类型是Some[Int]
。但是在我上面的代码片段中,它是 Sig#Option[Int]
,我想改为 Sig#Some[Int]
。
所以我尝试了以下内容以更接近 scalac 为其案例生成的内容 类:
trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>
object Some {
def unapply[A](s: Sig#Some[A]): scala.Option[A] =
fold(s)(scala.None, a => scala.Some(a))
}
object None {
def unapply(n: Sig#None): Option[Unit] =
fold(n)(scala.Some(()), (_: Any) => scala.None)
}
}
但现在我收到如下警告:
[warn] Main.scala:78: abstract type pattern Sig#None is unchecked since it is eliminated by erasure
[warn] case None(_) => sys.error("")
我不确定为什么会这样,因为 Sig#None
是 Sig#Option[Int]
的子类型,并且 在编译时已知 。
运行时也还可以,但推断的类型仍然不是我期望的类型。
所以问题是
- 尽管有子类型信息,为什么这里提到类型擦除?
- 如何在
(some(42): Sig#Option[Int]) match { case s @ Some(42) => s }
中为
s
获得 Sig#Option[Int]
不幸的是,你不能如愿以偿。问题是 scalac 知道 Sig#None <: Sig#Option[A]
是不够的,它必须能够验证它传递给 unapply
的值实际上是 Sig#None
。这适用于 scala.Option
,因为编译器可以在将类型传递给 [=12= 之前生成 instanceof
检查以验证类型实际上是 Some
还是 None
] 方法。如果检查失败,它 跳过 该模式(并且永远不会调用 unapply
)。
在你的例子中,由于 scalac 只知道 opt
是一个 Sig#Option[Int]
,它不能做任何事情来保证该值实际上是一个 Some
或 None
在将其传递给 unapply
.
那么它有什么作用呢? 无论如何它都会传递值!这是什么意思?好吧,让我们稍微修改一下提取器:
trait Extractors[Sig <: OptionSig] { self: OptionOps[Sig] =>
object Some {
def unapply[A](s: Sig#Some[A]): scala.Option[A] =
fold(s)(scala.None, a => scala.Some(a))
}
object None {
def unapply(n: Sig#None): Option[Unit] =
scala.Some(())
}
}
我们所做的只是停止在 None
情况下使用 fold
。既然我们 知道 参数必须是 Sig#None
,为什么还要调用 fold
,对吧?我的意思是,我们不希望这里传递 Sig#Some
,对吧?
当我们 运行 这个例子时,你会点击 RuntimeException
,因为我们的第一个模式匹配 成功 并调用 ???
.在 scala.Option
的情况下,模式会失败,因为它受到生成的 instanceof
检查的保护。
我举了另一个例子来展示另一个危险,我们在 Sig#Some
中添加了一个小约束,这让我们也避免了 Some
情况下的 fold
:https://gist.github.com/tixxit/ab99b741d3f5d2668b91
无论如何,您的具体案例在技术上是安全的。我们知道您使用了 fold
,因此使用 Sig#Option
是安全的。问题是 scalac
不知道。