ChatGPT解决这个技术问题 Extra ChatGPT

What is a sealed trait?

Sealed classes are described in 'Programming in Scala', but sealed traits are not. Where can I find more information about a sealed trait?

I would like to know, if a sealed trait is the same as a sealed class? Or, if not, what are the differences? When is it a good idea to use a sealed trait (and when not)?


A
Adi Inbar

A sealed trait can be extended only in the same file as its declaration.

They are often used to provide an alternative to enums. Since they can be only extended in a single file, the compiler knows every possible subtypes and can reason about it.

For instance with the declaration:

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

The compiler will emit a warning if a match is not exhaustive:

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

So you should use sealed traits (or sealed abstract class) if the number of possible subtypes is finite and known in advance. For more examples you can have a look at list and option implementations.


it took me six months to randomly arrive here and understand how to replace Java Enum in Scala.
very nice ! and not only finite and known in advance but also part of a restricted (sealed ?) context where makes sense to check all possible subtypes like yes | no , even | odd etc...
Scala3 finally got enum alvinalexander.com/scala/…
D
Daniel C. Sobral

a sealed trait is the same as a sealed class ?

As far as sealed goes, yes. They share the normal differences between trait and class, of course.

Or, if not, what are the differences ?

Moot.

When is it a good idea to use a sealed trait (and when not) ?

If you have a sealed class X, then you have to check for X as well as any subclasses. The same is not true of sealed abstract class X or sealed trait X. So you could do sealed abstract class X, but that's way more verbose than just trait and for little advantage.

The main advantage of using an abstract class over a trait is that it can receive parameters. That advantage is particularly relevant when using type classes. Let's say you want to build a sorted tree, for instance. You can write this:

sealed abstract class Tree[T : Ordering]

but you cannot do this:

sealed trait Tree[T : Ordering]

since context bounds (and view bounds) are implemented with implicit parameters. Given that traits can't receive parameters, you can't do that.

Personally, I prefer sealed trait and use it unless some particular reason makes me use a sealed abstract class. And I'm not talking about subtle reasons, but in-your-face reasons you cannot ignore, such as using type classes.


"since context bounds (and view bounds) are implemented with implicit parameters." - could you elaborate on that?
@Ruby – pretty late reply, but in case you or anyone else is interested: context bounding ([A: F]) doesn't work the same way as variance constraints. Rather, it's syntactic sugar that demands an implicit F[A] in scope. It's generally used to summon typeclass instances in a way that's a bit terser and easier to read than an implicit parameter ((implicit fa: F[A])), but it still works the exact same way under the hood, and as Daniel points out, traits don't get to do that.
At the time of writing this is the only answer that answers the question (sealed trait vs sealed class). The other answers are answering a different question that was not asked (sealed vs non-sealed).
B
Brian Agnew

From the daily-scala blog:

When a trait is "sealed" all of its subclasses are declared within the same file and that makes the set of subclasses finite which allows certain compiler checks.


Thank you. With "all of its subclasses" it means classes and traits ?
@John - I've not tried, but I suspect classes. The point about sealing is that everything is defined within that one source unit
@JohnThreepwood: classes, traits and objects. Most of the time, in Scala, the term "class" is used to refer to classes, traits and objects. Only when talking about the specific differences between them, it means only classes. The SLS uses the term "template" to refer to both classes and traits, but that term isn't used much outside of the SLS, and there is no term that encompasses all three of classes, traits and objects.
A
A T

Also I feel the need to point you to the specifications:

The sealed modifier applies to class definitions. A sealed class may not be directly inherited, except if the inheriting template is defined in the same source file as the inherited class. However, subclasses of a sealed class can be inherited anywhere. — M. Odersky. The Scala language specification, version 2.8. online, Sept., 2013.


M
Majid Hosseini

‌‌Briefly:

Sealed traits can only be extended in the same file

List this lets the compiler easily know all possible subtypes

Use sealed traits when the number of possibly subtypes is finite and known in advance

A way of creating something like enum in Java

Help to define algebraic data types (ADTs)

and for more details Everything about sealed traits in Scala


C
Chema

Traits can also be defined sealed, and only extended by a fixed set of case classes. The core difference between normal traits and sealed traits can be summarized as follows:

Normal traits are open, so any number of classes can inherit from the trait as long as they provide all the required methods, and instances of those classes can be used interchangeably via the trait's required methods. A normal trait hierarchy makes it easy to add additional sub-classes: just define your class and implement the necessary methods. However, it makes it difficult to add new methods: a new method needs to be added to all existing subclasses, of which there may be many.

Sealed traits are closed: they only allow a fixed set of classes to inherit from them, and all inheriting classes must be defined together with the trait itself in the same file or REPL command. A sealed trait hierarchy is the opposite: it is easy to add new methods, since a new method can simply pattern match on each sub-class and decide what it wants to do for each. However, adding new sub-classes is difficult, as you need to go to all existing pattern matches and add the case to handle your new sub-class.

As an example

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

In general, sealed traits are good for modelling hierarchies where you expect the number of sub-classes to change very little or not-at-all. A good example of something that can be modeled using sealed trait is JSON.

A JSON value can only be JSON null, boolean, number, string, array, or dictionary.

JSON has not changed in 20 years, so it is unlikely that anyone will need to extend our JSON with additional subclasses.

While the set of sub-classes is fixed, the range of operations we may want to do on a JSON blob is unbounded: parse it, serialize it, pretty-print it, minify it, sanitize it, etc. Thus it makes sense to model a JSON data structure as a closed sealed trait hierarchy rather than a normal open trait hierarchy.

  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

The question was asking about the difference between sealed trait vs sealed class, but this is answering a different question (the difference between sealed vs non-sealed traits).