scalascala-3type-alias

Does scala 3 have true type alias? How to implement it?


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?


Solution

  • 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:

    https://scala-lang.org/files/archive/spec/2.13/04-basic-declarations-and-definitions.html#variance-annotations

    • 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
    
    }