Transparent Traits and Classes
Traits are used in two roles:
- As mixins for other classes and traits
- As types of vals, defs, or parameters
Some traits are used primarily in the first role, and we usually do not want to see them in inferred types. An example is the
Product trait that the compiler adds as a mixin trait to every case class or case object. In Scala 2, this parent trait sometimes makes inferred types more complicated than they should be. Example:
trait Kind case object Var extends Kind case object Val extends Kind val x = Set(if condition then Val else Var)
Here, the inferred type of
Set[Kind & Product & Serializable] whereas one would have hoped it to be
Set[Kind]. The reasoning for this particular type to be inferred is as follows:
- The type of the conditional above is the union type
Val | Var. This union type is treated as "soft", which means it was not explicitly written in the source program, but came from forming an upper bound of the types of some alternatives.
- A soft union type is widened in type inference to the least product of class or trait types that is a supertype of the union type. In the example, this type is
Kind & Product & Serializablesince all three traits are super-traits of both
Var. So that type becomes the inferred element type of the set.
Scala 3 allows one to mark a trait or class as
transparent, which means that it can be suppressed in type inference. Here's an example that follows the lines of the code above, but now with a new transparent trait
S instead of
transparent trait S trait Kind object Var extends Kind, S object Val extends Kind, S val x = Set(if condition then Val else Var)
x has inferred type
Set[Kind]. The common transparent trait
S does not appear in the inferred type.
In the previous example, one could also declare
transparent trait Kind
The widened union type of
if condition then Val else Var would then only contain the transparent traits
S. In this case, the widening is not performed at all, so
x would have type
Set[Val | Var].
The root classes and traits
Matchable are considered to be transparent. This means that an expression such as
if condition then 1 else "hello"
will have type
Int | String instead of the widened type
Which Traits and Classes Are Transparent?
Traits and classes are declared transparent by adding the modifier
transparent. Scala 2 traits and classes can also be declared transparent by adding a
@transparentTrait annotation. This annotation is defined in
scala.annotation. It will be deprecated and phased out once Scala 2/3 interoperability is no longer needed.
The following classes and traits are automatically treated as transparent:
scala.Any scala.AnyVal scala.Matchable scala.Product java.lang.Object java.lang.Comparable java.io.Serializable
Typically, transparent types other than the root classes are traits that influence the implementation of inheriting classes and traits that are not usually used as types by themselves. Two examples from the standard collection library are:
IterableOps, which provides method implementations for an
StrictOptimizedSeqOps, which optimises some of these implementations for sequences with efficient indexing.
Generally, any trait that is extended recursively is a good candidate to be declared transparent.
Rules for Inference
Transparent traits and classes can be given as explicit types as usual. But they are often elided when types are inferred. Roughly, the rules for type inference imply the following.
- Transparent traits are dropped from intersections where possible.
- Union types are not widened if widening would result in only transparent supertypes.
The precise rules are as follows:
When inferring a type of a type variable, or the type of a val, or the return type of a def,
where that type is not higher-kinded,
Bis its known upper bound or
Anyif none exists:
If the type inferred so far is of the form
T1 & ... & Tnwhere
n >= 1, replace the maximal number of transparent traits
Any, while ensuring that the resulting type is still a subtype of the bound
However, do not perform this widening if all types
Tican get replaced in that way. This clause ensures that a single transparent trait instance such as
Productis not widened to
Any. Transparent trait instances are only dropped when they appear in conjunction with some other type.
If the original type was a is union type that got widened in a previous step to a product consisting only of transparent traits and classes, keep the original union type instead of its widened form.