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

131 Views Asked by At

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?

2

There are 2 best solutions below

0
Dmytro Mitin On BEST ANSWER

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

}
5
tribbloid On

Found a possible solution:


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] {

    private type TakeAlias = List[Seq[(T, T)]]

    def take1: TakeAlias

    def take2: TakeAlias

    def take3: TakeAlias
  }
}

Alternatively:


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] {

    opaque type TakeAlias = List[Seq[(T, T)]]

    def take1: TakeAlias

    def take2: TakeAlias

    def take3: TakeAlias
  }

  trait Aliased2[+T] extends Aliased[T] {

    def take4: TakeAlias
  }
}