How to translate type projections into PDTs in implicits?

82 views Asked by At

Here is one idiomatic scala 2 example:

trait Box {
    type Content
    val content :Content
}

implicit class BoxExtension[B <: Box](private val self :B) extends AnyVal {
    def map[O](f :self.Content => O)(implicit mapper :Mapper[B, O]) :mapper.Res =
        mapper(self)(f)
}


trait Mapper[B <: Box, O] {
    type Res
    def apply(box :B)(f :box.Content => O) :Res
}

As you see, it has already been partly converted to the usage of path dependent types. But how to create an implicit value of Mapper for an O type which references self.Content (such as O =:= self.Content itself)?. Ideally a solution which would have straightforward equivalence between Scala 2 and Scala 3.

1

There are 1 answers

3
username On BEST ANSWER

You can make an implicit def for this. Since Dotty doesn't allow type projections on abstract types, you'll need an extra type parameter. Also, I had to make self public because it was used in the signature of map.

object Mapper {
  implicit def uselessMapper[B <: Box{type Content = C}, C]: Mapper[B, C] { type Res = AllPurposeBox[C] } =
    new Mapper[B, C] {
      type Res = AllPurposeBox[C]
      def apply(box: B)(f: box.Content => C) =
        new AllPurposeBox(f(box.content))
    }
}

class AllPurposeBox[T](override val content: T) extends Box {
  type Content = T
}

Full example

I would normally suggest using type parameters for Box and (and an extra one for Mapper) instead, but it gets a little complicated. Perhaps you could turn BoxExtension into something like this, with C as an extra parameter:

implicit class BoxExtension[B <: Box {type Content = C}, C](private val self: B) extends AnyVal {
  def map[O](f: C => O)(implicit mapper: Mapper[B, O]): mapper.Res =
    mapper(self)(f)
}

If you're open to using just Dotty and not cross-compiling, you can do this

trait Mapper[B <: Box, O] {
  type Res
  def apply(box: B)(f: box.Content => O): Res
  extension (self: B) def map(f: self.Content => O): Res = apply(self)(f)
}
object Mapper {
  given uselessMapper[B <: Box{type Content = C}, C] as Mapper[B, C] {
    type Res = AllPurposeBox[C]
    def apply(box: B)(f: box.Content => C) = new AllPurposeBox(f(box.content))
  }
}