Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does Dotty offer to replace type projections?

I have been reading about Dotty, since it looks like it is about to become scala 3, and noticed that type projections are deemed "unsound" and removed from the language ...

This seems like a bummer, as I have seen several use cases where they were really useful. For example:

trait Contents
class Foo extends Contents
class Bar extends Contents

trait Container[T <: Contents] { type ContentType = T }
class FooContainer extends Container[Foo]
class BarContainer extends Container[Bar]

trait Manager[T <: Container[_]] { 
  type ContainerType = T 
  type ContentType = T#ContentType
  def getContents: ContentType 
  def createContainer(contents: ContentType): ContainerType
}

How would one do something like this in Dotty? Add a second type parameter to Manager? But, aside from the fact that it makes it really tedious to create and manipulate instances of the Manager, it also doesn't quite work, as there is no way to enforce the relationship between the two types (Manager[FooContainer, Bar] should not be legal).

Then, there are other uses, like type lambdas, and partially applied types, that are useful for creating biased functors etc ... Or do these (partially applied types) become "first class citizens" in Dotty?

EDIT

To answer the question in the comments, here is a somewhat contrived example of his this may be used. Let's suppose, my Managers are actually Akka Actors:

abstract class BaseManager[T <: Container[_]](
  val storage: ContentStorage[T#ContentType]
) extends Actor with Manager[T] {
    def withContents(container: T, content: ContentType): ContainerType
    def withoutContents: T

    var container: T = withoutContents

    def receive: Receive {
       case ContentsChanged => 
          container = withContents(container, storage.get)
       case ContainerRequester => 
           sender ! container
       // ... other common actions 
    }
}

class FooManager(storage: FooStorage) extends BaseManager[FooContainer](storage) {
   def withContents(container: FooContainer, content: Foo) = 
       container.copy(Some(content))
   def withoutContent = FooContainer(None)

   override def receive: Receive = super.receive orElse { 
    // some additional actions, specific to Foo
   }
}

case class FooContainer(content: Option[Foo]) extends Container[Foo]{
  // some extremely expensive calculations that happen when 
  // content is assigned, so that we can cache the result in container
}
like image 821
Dima Avatar asked Apr 26 '18 12:04

Dima


1 Answers

In Scala 2.12 type projections sometimes can be replaced with type class + path-dependent types

trait ContentType[T <: Container[_]] {
  type Out
}
object ContentType {
  type Aux[T <: Container[_], Out0] = ContentType[T] { type Out = Out0 }
  def instance[T <: Container[_], Out0]: Aux[T, Out0] = new ContentType[T] { type Out = Out0 }

  implicit def mk[T <: Contents]: Aux[Container[T], T] = instance
}

abstract class Manager[T <: Container[_]](implicit val contentType: ContentType[T]) {
  type ContainerType = T
  def getContents: contentType.Out
  def createContainer(contents: contentType.Out): ContainerType
}

Checked in Dotty 0.16.0-bin-20190529-3361d44-NIGHTLY (in 0.16.0-RC3 delegate should be instead of implied)

trait Contents
class Foo extends Contents
class Bar extends Contents

trait Container[T <: Contents] { type ContentType = T }
class FooContainer extends Container[Foo]
class BarContainer extends Container[Bar]

trait ContentType[T <: Container[_]] {
  type Out
}
object ContentType {
  implied [T <: Contents] for ContentType[Container[T]] {
    type Out = T
  }
}

trait Manager[T <: Container[_]] given (val contentType: ContentType[T]) {
  type ContainerType = T
  type ContentType = contentType.Out
  def getContents: ContentType
  def createContainer(contents: ContentType): ContainerType
}

One more option is to use match types

trait Contents
class Foo extends Contents
class Bar extends Contents

trait Container[T <: Contents] { type ContentType = T }
class FooContainer extends Container[Foo]
class BarContainer extends Container[Bar]

type ContentType[T <: Container[_]] = T match {
  case Container[t] => t
}

trait Manager[T <: Container[_]] {
  type ContainerType = T
  def getContents: ContentType[T]
  def createContainer(contents: ContentType[T]): ContainerType
}
like image 67
Dmytro Mitin Avatar answered Nov 13 '22 22:11

Dmytro Mitin