In Scala 3, how to replace General Type Projection that has been dropped? - scala

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?

You can try match types
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

Related

Why is it possible to define a class with a unreified path-dependent type?

Considering the following example:
trait Supe {
type Out <: Supe
def out: Out
}
class Reif1 extends Supe {
type Out = Reif1
override def out: Out = this
}
class Reif2 extends Supe {
type Out >: this.type <: Reif2
override def out: Out = this
}
class Reif1 should obviously work. as type Out is reified and becomes a type alias
class Reif2 also works but seriously? type Out only has upper/lower bound defined and the bound was not even tight enough: this.type is a singleton type, and Reif2 is a class type. So what exactly would Out look like if Reif2 got instantiated? Is it going to be this.type? Or Reif2? But the bigger question should be: why scalac 2.12/2.13 allows it be compiled?
In Scala (or in DOT calculus 1 2) all types are intervals.
type Out = Reif1 is (or is supposed to be) type Out >: Reif1 <: Reif1.
Abstract type without bounds type Out is type Out >: Nothing <: Any.
So what exactly would Out look like if Reif2 got instantiated?
It will remain exactly type Out >: this.type <: Reif2
val r = new Reif2
import scala.reflect.runtime.universe._
typeOf[r.Out] // App.r.Out
showRaw(typeOf[r.Out]) // TypeRef(SingleType(ThisType(App), TermName("r")), TypeName("Out"), List())
typeOf[r.Out].typeSymbol.isAbstract // true
typeOf[r.Out].typeSymbol.typeSignature // >: Reif2.this.type <: App.Reif2
If you replace type Out >: this.type <: Reif2 in Reif2 with type Out = this.type (or type Out = Reif2 or type Out = Supe) then isAbstract will return false.
Abstract type member of a singleton object (see application of abstract type)
What is the meaning of a type declaration without definition in an object? (see why it's not easy to check that type T >: L <: U is not abstract)
Concrete classes can have abstract type members #1753
SI-8217 allow abstract type members in objects #4024
Abstract type members are incorrectly forbidden in objects (unless inherited) #8217
Use of abstract type in a concrete class?
Concrete classes with abstract type members
What's different between "def apply[T](c:T)" and "type T;def apply(c:T)"

Two related existential type parameters

Let's take this example code:
trait DataProcessor[D] {
def computeData(): D
}
case class DataItem[D, P <: DataProcessor[D]](processor: P, data: D)
def computeDataFromItems(items: Set[DataItem[_, _]]) = items collectFirst {
case DataItem(s: DataProcessor[_], d) =>
s.computeData()
}
When compiled with Scala 2.11, it yield this error:
Error:(8, 77) type arguments [_$1,_$2] do not conform to class DataItem's type
parameter bounds [D,P <: TwoExistentials.DataProcessor[D]]
def computeDataFromItems(items: Set[DataItem[_, _]]) = items collectFirst {
It seems that the collectFirst method needs some more type information, that is not contained in the pair of existential types of the DataItem class.
Is there a chance to give more specific types to the method's arguments, something like:
def computeDataFromItems(items: Set[DataItem[D, P] forSome { type D, type P <: DataProcessor[D] }])
which unfortunately doesn't compile?
Otherwise, is there a way to allow for a class with two related type parameters to be used in collections, in a type-safe manner?
By the way, I have already considered using shapeless, instead of existential types. But I am looking for an implementation with classical Scala collections.
This should compile (replaced , with ;):
def computeDataFromItems(items: Set[DataItem[D, P] forSome { type D; type P <: DataProcessor[D] }])

Covariance contravariance error in Scala

I tried to compile the following piece of code:
class A[-T]
class B[-T] extends A[A[T]]
I obtain the following error:
error: contravariant type T occurs in covariant position in type [-T]A[A[T]]{def <init>(): B[T]} of class B
Why is this an error?
This is an error because A[A[T]] is covariant in type T.
Definition of covariant type from Wikipedia:
Within the type system of a programming language, a typing rule or a
type constructor is:
covariant if it preserves the ordering of types (≤), which orders types from more specific to more generic;
contravariant if it reverses this ordering;
Consider the following:
class Fruit
class Banana extends Fruit
and
Banana <: Fruit
A[Banana] :> A[Fruit] // Because A is contravariant in type T
A[A[Banana]] <: A[A[Fruit]] // Again because A is contravariant in type T
The last statement means that A[A[T]] is covariant since it preserves the ordering of types from more specific to more generic.
So it possible to do:
scala> type AA[+T] = A[A[T]]
defined type alias AA
scala> type AAA[-T] = A[A[A[T]]]
defined type alias AAA
But the following will lead to error:
scala> type AA[-T] = A[A[T]]
<console>:15: error: contravariant type T occurs in covariant position in type [-T]A[A[T]] of type AA
type AA[-T] = A[A[T]]
^
scala> type AAA[+T] = A[A[A[T]]]
<console>:15: error: covariant type T occurs in contravariant position in type [+T]A[A[A[T]]] of type AAA
type AAA[+T] = A[A[A[T]]]
^
And finally returning to the original question there is the same violation of the variance rule in class B definition since the base class A[A[T]] is covariant in type T by construction.
To understand why it is prohibited let's assume it is possible:
class B[-T] extends A[A[T]]
In this case we get:
B[Fruit] <: B[Banana] <: A[A[Banana]]
so
val fruits: B[Fruit] = new B[Fruit]
val bananas1: B[Banana] = fruits
val bananas2: A[A[Banana]] = bananas1
Now we have a value bananas2 of type A[A[Banana]] which points to an instance of A[A[Fruit]] which violates type-safety since A[A[Banana]] <: A[A[Fruit]].
<init> is the constructor of A (or B), defined by the compiler.
The problem with the code is that B[-T] extends A[A[T]] leads to a contradiction.
Take this example:
class A[-T]
class B[-T] extends A[A[T]]
class Animal
class Cat extends Animal
For short-hand, let's use A <: B to signify that A is a sub-type of B.
Since both A and B are contravariant over their type parameters T, and Cat <: Animal, then
A[Animal] <: A[Cat]
B[Animal] <: B[Cat]
Since A[Animal] <: A[Cat], then (again because of the contravariance of A):
A[A[Cat]] <: A[A[Animal]]
Also, by the definition of B:
B[Cat] = A[A[Cat]]
B[Animal] = A[A[Animal]]
We already know that B[Animal] <: B[Cat], but if we translate these types to their above equivalances, we get:
B[Animal] <: B[Cat] => A[A[Animal]] <: A[A[Cat]]
But we already showed that A[A[Cat]] <: A[A[Animal]]. We end up with an impossible situation.

Relationship between parameterized class with different type, Scala

In Scala,
What is the relationship between parameterized class with different type?
For example,
class A[T]
What is the relationship between A[Int] (or A[String]) and A?
and what is the relationship between A[Int] and A[String] ?
I want to know, because I would like to do something like
case class A[T](v: T) { def print = println(v) }
def iter(s: Seq[A[???]]) = s.map(print) // What is the proper annotation of '???'
iter(Seq(A(1), A("Hello")) // 1 Hello
What is the relationship between A[Int] and A?
A is a type constructor of kind * → *, and A[Int] is one of the possible results of apply a type (Int) to the type constructor A.
What is the relationship between A[Int] and A[String]?
The least upper bound between these two types is A[_ >: String with Int], which can only be instantiated to A[Any] since it's the only super class of String and Int.
What is the proper annotation of '???'
In your example that would be Any, or a type parameter you could add to your def iter that would itself be instantiated to Any.
Types are related using Variance.
To answer your question, Java's wildcards equivalent in Scala is Existential types. So you can specify something like this:
def iter(s: Seq[A[_]]) = s.map(_.print)
which is equivalent to:
def iter(s: Seq[A[T] forSome {type T}]) = s.map(_.print)
You can also specify bounds like below:
def iter(s: Seq[A[_ <: CharSequence]]) = s.map(_.print)
println(iter(Seq(A[StringBuilder](new StringBuilder("Test")), A[String]("Hello"))))
[Explicit type specified in constructors to avoid implicit conversion to required type CharSequence].
Note that the following would not compile though:
def iter(s: Seq[A[CharSequence]]) = s.map(_.print)
println(iter(Seq(A[StringBuilder](new StringBuilder("Test")), A[String]("Hello"))))
This is where variance specification in class would help:
case class A[+T](v: T) {
def print = v
}
def iter(s: Seq[A[CharSequence]]) = s.map(_.print)
println(iter(Seq(A[StringBuilder](new StringBuilder("Test")), A[String]("Hello"))))

Scala: using type parameters or abstract types as type bounds

Suppose I have:
class Bounded[A] {
type apply[C <: A] = C
}
This compiles:
implicitly[Bounded[Any]#apply[String] =:= String]
This fails:
type Str = Bounded[Any]#apply[String]
...with:
[error] /home/grant/Workspace/scunits/test/src/main/scala/Box.scala:37: type arguments[String] do not conform to type apply's type parameter bounds [C <: A]
[error] type Str = Bounded[Any]#apply[String]
[error] ^
I tried using abstract types instead of type parameters, with the same result. The only work-around I found was to instantiate the type. This compiles:
val boundedAny = new Bounded[Any]
type Str2 = boundedAny.apply[String]
Unfortunately I'm working with phantom types which don't have run time instances, often for performance reasons.
Why does Scala produce a compile error here? Is there a better work-around?
Thanks for any help.
Update: In addition to the workaround below, I needed a way to override types with abstract type bounds. I did this like so:
object Test {
class AbstractBounded[A] {
type apply[C <: A] <: A
class Workaround[C <: A] {
type go = apply[C]
}
}
class Bounded[A] extends AbstractBounded[A] {
type apply[C <: A] = C
}
type Str = Bounded[Any]#Workaround[String]#go
}
How about:
scala> class Bounded[A] { class i[C <: A]{ type apply = C}}
defined class Bounded
scala> type TTT = Bounded[Any]#i[String]#apply
defined type alias TTT
scala> implicitly[TTT =:= String]
res4: =:=[TTT,String] = <function1>
Scala forgot to lookup generic (or another "abstract" type) before binding parameter to type alias. Given that =:= works fine - seems like a bug for me. Maybe implicits are resolving on another compilation level or just before this check.