In many Scala tutorials & marketing materials, I found many people abusing terminology by mingling "type alias" and "dependent type", while in reality they are not the same thing.
E.g. in the following example, TakeAlias
is a dependent type, not a type alias. As a result, it will cause compilation to fail:
object TrueTypeAlias {
trait Unaliased[+T] {
def take1: List[Seq[(T, T)]]
def take2: List[Seq[(T, T)]]
def take3: List[Seq[(T, T)]]
}
trait Aliased[+T] {
type TakeAlias = List[Seq[(T, T)]]
def take1: TakeAlias
def take2: TakeAlias
def take3: TakeAlias
}
}
/*
TrueTypeAlias.scala:16:10: covariant type T occurs in invariant position in type = List[Seq[(T, T)]] of type TakeAlias
one error found
*/
The problem is: what does it take to implement true type alias? Is there a compiler mechanism/extension I can use to make it work?
type TakeAlias = List[Seq[(T, T)]]
is a type alias in the sense that TakeAlias
can be used instead of List[Seq[(T, T)]]
.
At the same time type TakeAlias = ...
is a type member of the trait. So x.TakeAlias
(for x: Aliased[T]
) is a path-dependent type. Although this path dependency is now trivial: x.TakeAlias
are the same for different x: Aliased[T]
with the same T
, namely all x.TakeAlias
are List[Seq[(T, T)]]
. And since type TakeAlias = ...
is a type member, all variance-position restrictions are applied.
I'd fix this code making type alias generic
trait Aliased[+T] {
type TakeAlias[+S] = List[Seq[(S, S)]]
//type TakeAlias[S] = List[Seq[(S, S)]]
def take1: TakeAlias[T]
def take2: TakeAlias[T]
def take3: TakeAlias[T]
}
or extracting type alias outside (for example to companion)
trait Aliased[+T] {
import Aliased.*
def take1: TakeAlias[T]
def take2: TakeAlias[T]
def take3: TakeAlias[T]
}
object Aliased {
type TakeAlias[+S] = List[Seq[(S, S)]]
//type TakeAlias[S] = List[Seq[(S, S)]]
}
A quote from the spec:
- The right-hand side of a type alias is always in invariant position.
References to the type parameters in object-private or object-protected values, types, variables, or methods of the class are not checked for their variance position. In these members the type parameter may appear anywhere without restricting its legal variance annotations.
Also when you're sure that illegal usage can't lead to issues in your use case you can use scala.annotation.unchecked.uncheckedVariance
trait Aliased[+T] {
type TakeAlias = List[Seq[(T @uncheckedVariance, T @uncheckedVariance)]]
def take1: TakeAlias
def take2: TakeAlias
def take3: TakeAlias
}
or
trait Aliased[+T] {
type T1 = T @uncheckedVariance
type TakeAlias = List[Seq[(T1, T1)]]
def take1: TakeAlias
def take2: TakeAlias
def take3: TakeAlias
}