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
}
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With