为什么协变枚举上的匹配与密封特征的匹配不同?
Why does a match on covariant enum does not behave the same as with a sealed trait?
为什么会产生错误?
import scala.io.StdIn
enum Flow[+A] {
case Say (text: String) extends Flow[Unit]
case Ask extends Flow[String]
}
def eval [T](flow: Flow[T]): T = flow match {
case Flow.Say(text) => println(text)
case Flow.Ask => StdIn.readLine // error here
}
18 | case Flow.Ask => StdIn.readLine
| ^^^^^^^^^^^^^^
| Found: String
| Required: T
(如果枚举在 T 上保持不变则没有错误)
但是当枚举被替换为(看似等价?)具有密封特征的 ADT 时,不会报告此类错误:
sealed trait Flow[+A]
object Flow {
case class Say(text: String) extends Flow[Unit]
case object Ask extends Flow[String]
}
// match statement in eval works fine
我们可以查看 the initial proposal 向 Scala 3 添加枚举以获得一些见解。
根据该提案的措辞,枚举案例分为三类。
- Class cases are those cases that are parameterized, either with a type parameter section [...] or with one or more (possibly empty) parameter sections (...).
- Simple cases are cases of a non-generic enum class that have neither parameters nor an extends clause or body. That is, they consist of a name only.
- Value cases are all cases that do not have a parameter section but that do have a (possibly generated) extends clause and/or a body.
让我们看看您的 enum
声明。
enum Flow[+A] {
case Say (text: String) extends Flow[Unit]
case Ask extends Flow[String]
}
现在,简单的案例就出来了,因为你的枚举是通用的。您的 Say
很明显是一个 class 案例,因为它是参数化的。另一方面,Ask
是非泛型和非枚举的,因此它是一个值案例。如果我们向下滚动一点,我们会看到值案例的作用
(6) A value case
case C extends <parents> <body>
expands to a value definition
val C = new <parents> { <body>; def enumTag = n; $values.register(this) }
where n
is the ordinal number of the case in the companion object, starting from 0. The statement $values.register(this)
registers the value as one of the enumValues
of the enumeration (see below). $values
is a compiler-defined private value in the companion object.
这里的底线是 Ask
没有定义为
object Ask extends Flow[String]
但更像
val Ask = new Flow[String]() { ... }
所以你的模式匹配
case Flow.Ask => StdIn.readLine
实际上是针对 值 Flow.Ask
的 相等性检查 ,而不是针对单例对象的类型检查。不幸的是,前者无法向编译器证明您的值的泛型类型,因此它仍然是 T
,而不是专用于 String
.
同一页上提供了这样做的理由
Objectives
...
- Enumerations should be efficient, even if they define many values. In particular, we should avoid defining a new class for every value.
...
因此,Scala 开发人员似乎希望避免枚举声明大量未参数化值(即看起来像普通 Java 样式枚举的类型)所导致的膨胀。
那么简单的解决方案就是提供一个参数列表
case Ask() extends Flow[String]
使用起来稍微麻烦一些,但它确实定义了一个新类型,然后你的模式匹配
case Flow.Ask() => StdIn.readLine
会成功
为什么会产生错误?
import scala.io.StdIn
enum Flow[+A] {
case Say (text: String) extends Flow[Unit]
case Ask extends Flow[String]
}
def eval [T](flow: Flow[T]): T = flow match {
case Flow.Say(text) => println(text)
case Flow.Ask => StdIn.readLine // error here
}
18 | case Flow.Ask => StdIn.readLine
| ^^^^^^^^^^^^^^
| Found: String
| Required: T
(如果枚举在 T 上保持不变则没有错误)
但是当枚举被替换为(看似等价?)具有密封特征的 ADT 时,不会报告此类错误:
sealed trait Flow[+A]
object Flow {
case class Say(text: String) extends Flow[Unit]
case object Ask extends Flow[String]
}
// match statement in eval works fine
我们可以查看 the initial proposal 向 Scala 3 添加枚举以获得一些见解。
根据该提案的措辞,枚举案例分为三类。
- Class cases are those cases that are parameterized, either with a type parameter section [...] or with one or more (possibly empty) parameter sections (...).
- Simple cases are cases of a non-generic enum class that have neither parameters nor an extends clause or body. That is, they consist of a name only.
- Value cases are all cases that do not have a parameter section but that do have a (possibly generated) extends clause and/or a body.
让我们看看您的 enum
声明。
enum Flow[+A] {
case Say (text: String) extends Flow[Unit]
case Ask extends Flow[String]
}
现在,简单的案例就出来了,因为你的枚举是通用的。您的 Say
很明显是一个 class 案例,因为它是参数化的。另一方面,Ask
是非泛型和非枚举的,因此它是一个值案例。如果我们向下滚动一点,我们会看到值案例的作用
(6) A value case
case C extends <parents> <body>
expands to a value definition
val C = new <parents> { <body>; def enumTag = n; $values.register(this) }
where
n
is the ordinal number of the case in the companion object, starting from 0. The statement$values.register(this)
registers the value as one of theenumValues
of the enumeration (see below).$values
is a compiler-defined private value in the companion object.
这里的底线是 Ask
没有定义为
object Ask extends Flow[String]
但更像
val Ask = new Flow[String]() { ... }
所以你的模式匹配
case Flow.Ask => StdIn.readLine
实际上是针对 值 Flow.Ask
的 相等性检查 ,而不是针对单例对象的类型检查。不幸的是,前者无法向编译器证明您的值的泛型类型,因此它仍然是 T
,而不是专用于 String
.
同一页上提供了这样做的理由
Objectives
...
- Enumerations should be efficient, even if they define many values. In particular, we should avoid defining a new class for every value.
...
因此,Scala 开发人员似乎希望避免枚举声明大量未参数化值(即看起来像普通 Java 样式枚举的类型)所导致的膨胀。
那么简单的解决方案就是提供一个参数列表
case Ask() extends Flow[String]
使用起来稍微麻烦一些,但它确实定义了一个新类型,然后你的模式匹配
case Flow.Ask() => StdIn.readLine
会成功