In Scala 2 I used type projection in an simple ORM model which contains of IdEntity
and some Repositories. Now I tried to migrate to Scala 3 but stuck since type projection with #
is missing.
Simplified code looks like:
trait IdEntity:
type ID_Type
val id: ID_Type
trait IdLongEntity extends IdEntity:
override type ID_Type = Long
trait IdStringEntity extends IdEntity:
override type ID_Type = String
trait IdRepository[E <: IdEnity]:
def findById(id: E#ID_Type): Option[E]:
Any ideas how to overcome the missing type projection in def findById(id: E#ID_Type)
?
I already tried with type match like
object IdEntity:
type Aux[K] = IdEntity {type ID_Type = K}
type IdType[T <: IdEntity] = T match
case IdEntity.Aux[k] => k
but this does not work since the compile thinks that the types does not match.
Update:
Thanks to the answer I tried type classes as well as match type but both fail as soon as I try the following (https://scastie.scala-lang.org/UtW9u0mbS5ydiZmQNNWV6g):
trait IdEntity:
type ID_Type
val idDefault: ID_Type
val id: Option[ID_Type]
def idOrDefault: ID_Type = id.getOrElse(idDefault)
trait IdRepository[E <: IdEntity](using val e: E):
type ID_Type = e.ID_Type
def delete(id: ID_Type): Either[String, Int] = deleteImpl(id)
def delete(entity: E): Either[String, Int] = delete(entity.idOrDefault) // here the type error occures
protected def deleteImpl(id: ID_Type): Either[String, Int]
trait IdEntity:
type ID_Type
val id: ID_Type
object IdEntity:
type Aux[K] = IdEntity {type ID_Type = K}
trait IdLongEntity extends IdEntity:
override type ID_Type = Long
trait IdStringEntity extends IdEntity:
override type ID_Type = String
// match type
type IdType[T <: IdEntity] = T match
case IdEntity.Aux[k] => k
trait IdRepository[E <: IdEntity]:
def findById(id: IdType[E]): Option[E] = ???
https://scastie.scala-lang.org/QoV6Z7xRTOa6hVhAYOb0uw
this does not work since the compile thinks that the types does not match
You should prepare reproduction and we'll see whether compilation can be fixed.
trait IdEntity:
type ID_Type
val id: ID_Type
object IdEntity:
type Aux[K] = IdEntity {type ID_Type = K}
trait IdLongEntity extends IdEntity:
override type ID_Type = Long
trait IdStringEntity extends IdEntity:
override type ID_Type = String
// type class
trait IdType[T <: IdEntity]:
type Out
object IdType:
given [K, T <: IdEntity.Aux[K]]: IdType[T] with
type Out = K
trait IdRepository[E <: IdEntity]:
def findById(using idType: IdType[E])(id: idType.Out): Option[E] = ???
https://scastie.scala-lang.org/166gIUf3Rg2DwSgLlheXvQ
Or you can even try to use type classes starting from IdEntity
, for example making IdEntity
a type class:
trait IdEntity:
type ID_Type
val id: ID_Type
trait IdLongEntity extends IdEntity:
override type ID_Type = Long
trait IdStringEntity extends IdEntity:
override type ID_Type = String
trait IdRepository[E <: IdEntity](using val e: E):
def findById(id: e.ID_Type): Option[E]
https://scastie.scala-lang.org/ap4GJY9NQyOyt2eHwobXSg
In Scala 3, how to replace General Type Projection that has been dropped?
What does Dotty offer to replace type projections?
E is not a legal path since it is not a concrete type (Scala 3)
https://docs.scala-lang.org/scala3/reference/dropped-features/type-projection.html
Translation of
// scala 2.13, type projections
trait IdEntity {
type ID_Type
val idDefault: ID_Type
val id: Option[ID_Type]
def idOrDefault: ID_Type = id.getOrElse(idDefault)
}
trait IdRepository[E <: IdEntity] {
type ID_Type = E#ID_Type
def delete(id: ID_Type): Either[String, Int] = deleteImpl(id)
def delete(entity: E): Either[String, Int] = delete(entity.idOrDefault)
protected def deleteImpl(id: ID_Type): Either[String, Int]
}
can be
// scala 3, match types
trait IdEntity:
type ID_Type
val idDefault: ID_Type
val id: Option[ID_Type]
def idOrDefault: ID_Type = id.getOrElse(idDefault)
object IdEntity:
type Aux[K] = IdEntity {type ID_Type = K}
type IdType[T <: IdEntity] = T match
case IdEntity.Aux[k] => k
// unfortunately, E <: IdEntity.Aux[IdType[E]] doesn't work (match types are not lazy enough): Recursion limit exceeded. Maybe there is an illegal cyclic reference?
trait IdRepository[E <: IdEntity]:
type ID_Type = IdType[E]
def delete(id: ID_Type): Either[String, Int] = deleteImpl(id)
def delete(entity: E {type ID_Type = IdType[E]}): Either[String, Int] = delete(entity.idOrDefault)
protected def deleteImpl(id: ID_Type): Either[String, Int]
or
// scala 3, type classes (path-dependent types)
trait IdEntity:
type ID_Type
val idDefault: ID_Type
val id: Option[ID_Type]
def idOrDefault: ID_Type = id.getOrElse(idDefault)
trait IdRepository[E <: IdEntity](using val e: E):
type ID_Type = e.ID_Type
def delete(id: ID_Type): Either[String, Int] = deleteImpl(id)
def delete(entity: E {type ID_Type = e.ID_Type}): Either[String, Int] = delete(entity.idOrDefault)
protected def deleteImpl(id: ID_Type): Either[String, Int]