ChatGPT解决这个技术问题 Extra ChatGPT

什么是密封特性?

密封类在“Scala 编程”中进行了描述,但密封特性没有。我在哪里可以找到有关密封特性的更多信息?

我想知道,如果密封特征与密封类相同?或者,如果不是,有什么区别?什么时候使用密封特性是个好主意(什么时候不)?


A
Adi Inbar

sealed 特征只能在与其声明相同的文件中扩展。

它们通常用于提供 enums 的替代方案。由于它们只能在单个文件中扩展,因此编译器知道所有可能的子类型并且可以对其进行推理。

例如声明:

sealed trait Answer
case object Yes extends Answer
case object No extends Answer

如果匹配不完整,编译器将发出警告:

scala> val x: Answer = Yes
x: Answer = Yes

scala> x match {
     |   case No => println("No")
     | }
<console>:12: warning: match is not exhaustive!
missing combination            Yes

因此,如果可能的子类型的数量是有限的并且事先已知,则应该使用密封特征(或密封抽象类)。有关更多示例,您可以查看 listoption 实现。


我花了六个月的时间才随机到达这里并了解如何在 Scala 中替换 Java Enum。
非常好 !并且不仅是有限的和预先已知的,而且是受限(密封?)上下文的一部分,在该上下文中检查所有可能的子类型(如 yes | )是有意义的不,甚至|奇怪之类的……
Scala3 终于得到了枚举 alvinalexander.com/scala/…
D
Daniel C. Sobral

密封特征与密封类相同吗?

sealed 而言,是的。当然,它们共享 traitclass 之间的正常差异。

或者,如果不是,有什么区别?

没有实际意义。

什么时候使用密封特性是个好主意(什么时候不)?

如果您有 sealed class X,则必须检查 X 以及任何子类。 sealed abstract class Xsealed trait X 并非如此。所以你可以做 sealed abstract class X,但这比 trait 更冗长,而且没有什么优势。

使用 abstract class 而不是 trait 的主要优点是它可以接收参数。在使用类型类时,这一优势尤为重要。例如,假设您要构建一个排序树。你可以这样写:

sealed abstract class Tree[T : Ordering]

但你不能这样做:

sealed trait Tree[T : Ordering]

因为上下文边界(和视图边界)是使用隐式参数实现的。鉴于特征不能接收参数,你不能这样做。

就个人而言,我更喜欢 sealed trait 并使用它,除非某些特殊原因让我使用 sealed abstract class。而且我不是在谈论微妙的原因,而是您不能忽视的表面原因,例如使用类型类。


“因为上下文边界(和视图边界)是用隐式参数实现的。” - 你能详细说明一下吗?
@Ruby – 回复很晚,但如果您或其他任何人感兴趣:上下文边界 ([A: F]) 的工作方式与方差约束不同。相反,它是语法糖,需要在范围内隐含 F[A]。它通常用于以一种比隐式参数 ((implicit fa: F[A])) 更简洁且更易于阅读的方式来调用类型类实例,但它在幕后仍然以完全相同的方式工作,正如 Daniel 指出的那样,特征不会去做那件事。
在撰写本文时,这是回答问题的唯一答案(密封特征与密封类)。其他答案正在回答一个没有被问到的不同问题(密封与非密封)。
B
Brian Agnew

daily-scala blog

当一个特征被“密封”时,它的所有子类都在同一个文件中声明,这使得子类集是有限的,这允许某些编译器检查。


谢谢你。对于“所有子类”,它意味着类和特征?
@John - 我没试过,但我怀疑上课。关于密封的要点是,一切都在一个源单元中定义
@JohnThreepwood:类、特征和对象。大多数时候,在 Scala 中,术语“类”用于指代类、特征和对象。只有在谈论它们之间的具体差异时,它才意味着只有类。 SLS 使用术语“模板”来指代类和特征,但该术语在 SLS 之外使用不多,并且没有包含所有三个类、特征和对象的术语。
A
A T

我也觉得有必要指出你的规格:

密封修饰符适用于类定义。密封类不能直接继承,除非继承模板与被继承类定义在同一个源文件中。但是,密封类的子类可以在任何地方继承。 — M.奥德斯基。 Scala 语言规范,2.8 版。在线,2013 年 9 月。


M
Majid Hosseini

简要地:

密封特征只能在同一个文件中扩展

列出这个让编译器很容易知道所有可能的子类型

当可能的亚型数量有限且事先已知时,使用密封特征

一种在 Java 中创建类似枚举的方法

帮助定义代数数据类型 (ADT)

并了解更多详情Everything about sealed traits in Scala


C
Chema

特征也可以定义为密封,并且只能通过一组固定的 case classes 进行扩展。 正常特征密封特征的核心区别可以总结如下:

普通 trait 是开放的,因此任何数量的类都可以从 trait 继承,只要它们提供所有必需的方法,并且这些类的实例可以通过 trait 的必需方法互换使用。一个正常的 trait 层次结构可以很容易地添加额外的子类:只需定义你的类并实现必要的方法。但是,添加新方法变得困难:需要将新方法添加到所有现有子类中,其中可能有很多。

Sealed trait 是封闭的:它们只允许一组固定的类从它们继承,并且所有继承类必须与 trait 本身一起定义在同一个文件或 REPL 命令中。密封的 trait 层次结构则相反:添加新方法很容易,因为新方法可以简单地对每个子类进行模式匹配并决定要为每个子类做什么。但是,添加新的子类很困难,因为您需要转到所有现有的模式匹配并添加案例来处理您的新子类。

举个例子

object SealedTraits extends App{
  sealed trait Point
  case class Point2D(x: Double, y: Double) extends Point
  case class Point3D(x: Double, y: Double, z: Double) extends Point

  def hypotenuse(p: Point) = p match {
    case Point2D(x, y) => math.sqrt(x  x + y  y)
    case Point3D(x, y, z) => math.sqrt(x  x + y  y + z  z)
  }

  val points: Array[Point] = Array(Point2D(1, 2), Point3D(4, 5, 6))

  for (p <- points) println(hypotenuse(p))
  // 2.23606797749979
  // 8.774964387392123

通常,密封特征适用于对您期望子类数量变化很小或根本不变化的层次结构进行建模。可以使用 sealed trait 建模的一个很好的例子是 JSON

JSON 值只能是 JSON null、布尔值、数字、字符串、数组或字典。

JSON 已经 20 年没有改变,所以不太可能有人需要用额外的子类来扩展我们的 JSON。

虽然子类的集合是固定的,但我们可能想要对 JSON blob 执行的操作范围是无限的:解析它、序列化它、漂亮打印它、缩小它、清理它等等。因此,它是有意义的将 JSON 数据结构建模为封闭的密封特征层次结构,而不是普通的开放特征层次结构。

  sealed trait Json
  case class Null() extends Json
  case class Bool(value: Boolean) extends Json
  case class Str(value: String) extends Json
  case class Num(value: Double) extends Json
  case class Arr(value: Seq[Json]) extends Json
  case class Dict(value: Map[String, Json]) extends Json

问题是询问密封特性与密封类之间的区别,但这是在回答一个不同的问题(密封特性与非密封特性之间的区别)。