2

This code does not compile in Scala 3 since type projection on an abstract type is now invalid:

trait Entity:
  type Key

type Dictionary[T <: Entity] = Map[T#Key, T]

The compiler complains that T is abstract and type projection is therefore no more available:

T is not a legal path
since it is not a concrete type

How could you define the above Dictionary type in Scala 3?

  • AFAIK, that was essentially equivalent to `Map[Any, T]` in **Scala 2**; which is why it was removed. It wasn't doing anything useful at all. – Luis Miguel Mejía Suárez Jan 24 '23 at 20:49
  • Do you mean there was no actual enforcement of the type dependency between `Entity` type and its `Key` type? How come there is no way to express such a simple constraint? – Grégory Weinbach Jan 24 '23 at 21:01
  • What kind of constraint can it even enforce? `Key` can be anything. – Luis Miguel Mejía Suárez Jan 24 '23 at 21:06
  • 1
    In `Dictionary`, `Key` depends on actual `T` type. But I get your point: there is no guarantee that `Key` is actually defined unless `T` is a concrete type. – Grégory Weinbach Jan 24 '23 at 21:09
  • Even if `T` is a concrete type, `Key` may be still undefined. - Or, it may be defined as `Any`. - Or may be defined in a way such that the actual type can change for each instance. – Luis Miguel Mejía Suárez Jan 24 '23 at 21:10
  • I meant "there is no guarantee that `Key` is actually defined unless `T` is a concrete **class**". – Grégory Weinbach Jan 24 '23 at 21:18
  • Again, it doesn't matter if `T` is a concrete class: The `Key` type may not be defined - It may be defined as `Any` - Or, it may be defined in a way that the actual type will depend on each instance. – Luis Miguel Mejía Suárez Jan 24 '23 at 21:33
  • I don't understand how a concrete class `A` that would implement `Entity` trait and therefore define `Key` type would not put any constraint on `Dictionary[A]` (`type Key = Any` would be, of course, a very loose constraint!). – Grégory Weinbach Jan 24 '23 at 21:43
  • You don't have to define a type member to make a concrete class, not even to instantiate it :) They can be abstract... because they are always abstract. When you do `type T = Foo` that is actually just sugar syntax for `type T >: Foo <: Foo` – Luis Miguel Mejía Suárez Jan 24 '23 at 22:01
  • And when you just do `type T` it actually means `type T >: Nothing <: Any` so that is why that projection is mostly equivalent to just `Any` – Luis Miguel Mejía Suárez Jan 24 '23 at 22:35
  • @LuisMiguelMejíaSuárez *"AFAIK, that was essentially equivalent to `Map[Any, T]` in Scala 2"* Although `Map[T#Key, T]` can be similar to `Map[Any, T]` at runtime, at compile time they are quite different. You can always put `string -> t` into `Map[Any, T]` but apparently you can not always put it into `Map[T#Key, T]`. – Dmytro Mitin Jan 25 '23 at 02:31
  • @LuisMiguelMejíaSuárez *"which is why it was removed"* General type projections were removed (so far) for different reasons https://github.com/lampepfl/dotty/issues/1050 https://contributors.scala-lang.org/t/proposal-to-remove-general-type-projection-from-the-language/2812 https://lptk.github.io/programming/2019/09/13/type-projection.html https://github.com/lampepfl/dotty-feature-requests/issues/14 – Dmytro Mitin Jan 25 '23 at 02:31
  • @LuisMiguelMejíaSuárez *"It wasn't doing anything useful at all"* On contrary, I guess they were quite useful https://apocalisp.wordpress.com/2010/06/08/type-level-programming-in-scala/ Type projections and type classes were two ways to perform type-level calculations in Scala 2 (like match types and type classes are in Scala 3). – Dmytro Mitin Jan 25 '23 at 02:31
  • @LuisMiguelMejíaSuárez *" They can be abstract... because they are always abstract."* The thing is that figuring out whether a type is "not abstract" can be not easy https://stackoverflow.com/questions/54137522/what-is-the-meaning-of-a-type-declaration-without-definition-in-an-object Although there is no standard way to check that `L` and `U` are the same in implementations of `type T >: L <: U`, this can be checked with a macro (at least for simple `L` and `U` like in `type T >: Foo <: Foo`). – Dmytro Mitin Jan 25 '23 at 03:12
  • @LuisMiguelMejíaSuárez *"When you do type `T = Foo` that is actually just sugar syntax for type `T >: Foo <: Foo`"* This is a syntax sugar in DOT, but not literally a syntax sugar in implementations for performance reasons (although intended to be the same) https://scastie.scala-lang.org/Hl3ykwYNRTujc2BpESyy8A https://contributors.scala-lang.org/t/whats-in-a-type-alias/4001 – Dmytro Mitin Jan 25 '23 at 03:25
  • @DmytroMitin right I should not have used _"sugar syntax"_ there, rather _"equivalent"_. - Also, I didn't mean type projections were always useless, only in this case _(as for my limited understanding of the matter)_. – Luis Miguel Mejía Suárez Jan 25 '23 at 13:24

1 Answers1

4
trait Entity:
  type Key

object Entity:
  type Aux[K] = Entity { type Key = K }

// match type
type EntityKey[T <: Entity] = T match
  case Entity.Aux[k] => k // k being lower-case is significant

type Dictionary[T <: Entity] = Map[EntityKey[T], T]

I had to introduce Aux-type because match types seem not to work with refined types

type EntityKey[T <: Entity] = T match
  case Entity { type Key = k } => k // Not found: type k

Beware that on value level match types can work not so well as type projections: Scala 3: typed tuple zipping

  • Besides match types, also type classes can be an alternative to general type projections
trait Entity:
  type Key

// type class
trait EntityKey[T <: Entity]:
  type Out

object EntityKey:
  type Aux[T <: Entity, Out0] = EntityKey[T] { type Out = Out0 }

  given [K]: EntityKey.Aux[Entity { type Key = K }, K] = null

// replacing the type with a trait
trait Dictionary[T <: Entity](using val entityKey: EntityKey[T]):
  type Dict = Map[entityKey.Out, T]

What does Dotty offer to replace type projections?

https://users.scala-lang.org/t/converting-code-using-simple-type-projections-to-dotty/6516

Dotty cannot infer result type of generic Scala function taking type parameter trait with abstract type

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66