I've been mulling over a design problem in a library I'm working on, and I realized that using existential types may allow me to change my design in a way that simplifies many parts of my library. However, I can't quite seem to get it to work.
It seems to me that myBuilder conforms to the type MultiSignalBuilder[E, R] forSome { type E[+X] >: Element[X] }, where Element[X] is MultiSignalElement[X], but the compiler says it does't. It seems to have to do the fact that E is a higher-kinded type. Why doesn't this work, and is there a way to fix it?
class MultiSignalElement[+T] {
}
abstract class MultiSignal[+T] {
type Element[+X] <: MultiSignalElement[X]
val element : Element[T]
def transform[R[+X] <: MultiSignal[X]](builder : MultiSignalBuilder[E, R] forSome { type E[+X] >: Element[X] }) : R[T] =
builder.buildNew(element)
}
abstract class MultiSignalBuilder[-E[+X] <: MultiSignalElement[X], +R[+X] <: MultiSignal[X]] {
def buildNew[T](element : E[T]) : R[T]
}
object myBuilder extends MultiSignalBuilder[MultiSignalElement, MultiSignal] {
def buildNew[T](e : MultiSignalElement[T]) = new MultiSignal[T]() {
type Element[+X] = MultiSignalElement[X]
val element = e
}
}
val multiSignal = new MultiSignal[Int] {
type Element[+X] = MultiSignalElement[X]
val element = new MultiSignalElement()
}
multiSignal.transform(myBuilder) //type error on this line
multiSignal.transform[MultiSignal](myBuilder) //type error on this line
Let do step-by-step analysis.
First we have
def transform[R](builder : MultiSignalBuilder[E, R] forSome { type E[+X] >: Element[X] }) : Unit = { }
Which is equivalent to statement : there exists
type E[+X] >: Element[X]
For which we can define
def transform[E[+X] >: Element[X], R[+_]](builder : MultiSignalBuilder[E, R] ) : Unit = { }
Here we have an error
Error:(7, 18) covariant type X occurs in contravariant position in
type [+X] >: MultiSignal.this.Element[X] of type E
This is something. You are expecting your mysterious existential covariant type should be a supertype of another covariant type. I think this is the first thing which is freaking the compiler. Lets change relation to subtyping
def transform[E[+X] <: Element[X], R[+_]](builder : MultiSignalBuilder[E, R] ) : Unit = { }
Now we have
Error:(7, 56) type arguments [E,R] do not conform to class
MultiSignalBuilder's type parameter bounds [-E[+X] <:
MultiSignalElement[X],+R[+X] <: MultiSignal[X]]
So we forgot to require subtyping of MultiSignal[X] out of R parameter.
Lets change it
def transform[E[+X] <: Element[X], R[+X] <: MultiSignal[X]](builder : MultiSignalBuilder[E, R] ) : Unit = { }
Now
multiSignal.transform[MultiSignalElement,MultiSignal](myBuilder)
Is succesfully compiled.
Finally we could get back to existential version
def transform[R[+X] <: MultiSignal[X]](builder : MultiSignalBuilder[E, R] forSome {type E[+X] <: Element[X]}) : Unit = { }
With which
multiSignal.transform[MultiSignal](myBuilder)
Is succesfully compiled.
Sadly
multiSignal.transform(myBuilder)
Still is not compiled. I think there is too much type relations to resolve for the compiler.
Related
Problem:
trait UpperBound[O]
trait High[F[O] <: UpperBound[O]]
def canEqual(that :Any) = that.isInstanceOf[High[_]]
def high(h :High[_]) = ???
Does not compile, because scalac sees the _ type instead of a type constructor it expects. How to fix it, ideally without writing a novel?
Original question (before edits in reply to Dmytro's answer) had:
def canEqual(that :Any) = that.isInstanceOf[High[m forSome { type m[O] <: UpperBound[O] }]]
def high(h :High[m forSome { type m[O] <: UpperBound[O] }] = ???
Is there a shorter way of writing the above two methods by using some wildcard expression?
Simply using _ in High's type parameter position doesn't work as the kind doesn't match, and _[_] is not even a valid type expression.
If you make existential quantization outside High then it's just
type T = High[F] forSome { type F[O] <: UpperBound[O] }
def canEqual(that: Any) = that.isInstanceOf[T]
def high(h: T) = ???
If you make existential quantization inside High then since
implicitly[(n forSome { type n <: Upper}) =:= Upper]
implicitly[(m[O1] forSome { type m[O] <: UpperBound[O]}) =:= UpperBound[O1]]
(and vice versa) it's just High[UpperBound]
implicitly[High[m forSome { type m[O] <: UpperBound[O] }] =:= High[UpperBound]]
def canEqual(that: Any) = that.isInstanceOf[High[UpperBound]]
def high(h: High[UpperBound]) = ???
An existential type 𝑇 forSome { 𝑄 } where 𝑄 contains a clause type 𝑡[tps]>:𝐿<:𝑈 is equivalent to the type 𝑇′ forSome { 𝑄 } where 𝑇′ results from 𝑇 by replacing every covariant occurrence of 𝑡 in 𝑇 by 𝑈 and by replacing every contravariant occurrence of 𝑡 in 𝑇 by 𝐿.
https://scala-lang.org/files/archive/spec/2.13/03-types.html#simplification-rules
My scenario is like:
trait A {
type B
def foo(b: B)
}
trait C[D <: A] {
val d: D
def createB(): D#B
def bar() {
d.foo(createB)
}
}
In REPL, it complains
<console>:24: error: type mismatch;
found : D#B
required: C.this.d.B
a.bar(createB())
What's wrong with this ? And (if possible at all) how to correct this code ?
D#B is a type projection, and is not the same as d.B. You have a type mismatch because in foo, Bactually meant this.B, which as said is not the same as D#B (the latter being more general).
Informally, you can think of D#Bas representing any possible type that the abstract type B can take for any instance of D, while d.B is the type of B for the specific instance d.
See What does the `#` operator mean in Scala? and What is meant by Scala's path-dependent types? for some context.
One way to make it compile it is by changing createB's return type to d.B:
def createB(): d.B
However in many cases such a solution is too restrictive because you are tied to the specific instance d, which might not be what you had in mind.
Another solution is then to replace the abstract type with a type parameter (though it is more verbose):
trait A[B] {
def foo(b: B)
}
trait C[B, D <: A[B]] {
val d: D
def createB(): B
def bar() {
d.foo(createB)
}
}
Update given this answer I'm not sure whether this should be considered a bug or not
This is a bug: SI-4377. An explicit type ascription yields
trait C[D <: A] {
val d: D
def createB(): D#B
def bar() {
(d:D).foo(createB)
// [error] found : D#B
// [error] required: _3.B where val _3: D
}
}
which looks like the implementation leaking. There's a workaround which involves casting to an intersection type (dangerous, casting is wrong etc; see my other answer here)
trait A {
type B
def foo(b: B)
}
case object A {
type is[A0 <: A] = A0 {
type B = A0#B
}
def is[A0 <: A](a: A0): is[A0] = a.asInstanceOf[is[A0]]
}
trait C[D <: A] {
val d: D
def createB(): D#B
def bar() {
A.is(d).foo(createB) // use it here!
}
}
The following piece of code does not compile :
trait A[F] {
def find(x: Int): F
def fill(f: F): Unit
}
object TestA {
def test[T <: A[F] forSome { type F }](t: T) =
t.fill(t.find(0))
}
It returns the following compilation error :
test.scala:8: error: type mismatch;
found : (some other)F(in type T)
required: F(in type T)
t.fill(t.find(0))
^
However the following code complies just fine :
trait B[F] {
type R = F
def find(x: Int): R
def fill(f: R): Unit
}
object TestB {
def test[T <: B[F] forSome { type F }](t: T) =
t.fill(t.find(0))
}
I have two questions here :
I expect the fist piece of code to compile. Why does it not?
If there is a good reason why first piece of code does not compile, I would expect the second to not compile either, for the same reason. How then, does it compile successfully?
Is either of these a bug?
I don't know why the compiler differentiates the two pieces of code. Basically, the code doesn't compile because the type returned by find and the type expected by fill don't have to be the same F, at least if find and fill were called on two different objects.
You could make the first piece of code to compile with:
def test[T <: A[F], F](t: T) =
t.fill(t.find(0))
And you could make the second piece of code not to compile with:
def test[T <: B[F] forSome { type F }](t: T, u: T) =
t.fill(u.find(0))
This should be rather a comment than an answer, but I don't have 50 reputation yet.
To understand what's happening, let's look at simpler versions of TestA.test and TestB.test.
object TestA {
def test1(a: A[String]) = {
val s: String = a.find(0)
a.fill(s)
}
}
object TestB {
def test1(b: B[String]) = {
val r: b.R = b.find(0)
b.fill(r)
}
}
Notice how the type of the intermediate value s refers to String, while the type of the intermediate value r does not.
object TestA {
def test2(a: A[F] forSome {type F}) = {
val s: F forSome {type F} = a.find(0)
// type mismatch;
// found : s.type (with underlying type F forSome { type F })
// required: F
a.fill(s)
}
}
object TestB {
def test2(b: B[F] forSome {type F}) = {
val r: b.R = b.find(0)
b.fill(r)
}
}
Once we throw in the existentials, we end up with an intermediate value s whose type is equivalent to Any, and which thus isn't a valid input for a.fill. The intermediate type for r, however, is still b.R, and so it is still an appropriate input for b.fill. The reason its type is still b.R is because b.R doesn't refer to F, and so according to the simplification rules for existential types, b.R forSome {type F} is equivalent to b.R, in the same way that Int forSome {type F} is equivalent to Int.
Is either of these a bug?
Well, there is certainly a bug somewhere (as of scalac 2.11.7), because the following does not type check.
object TestB {
def test3(b: B[F] forSome {type F}) = {
val r: b.R forSome {type F} = b.find(0)
// type mismatch;
// found : F
// required: b.R
// (which expands to) F
b.fill(r)
}
}
So either I'm wrong to think that b.R does not refer to F, in which case b.R forSome {type F} is not equivalent to b.R and your TestB.test should not type check but it does, or b.R forSome {type F} is equivalalent to b.R, in which case my TestB.test3 should type check but it doesn't.
I'm quite convinced that the bug is with the latter, because the error even occurs when the existential quantification has nothing to do with b.R, as in the following example.
object TestB {
def test4(b: B[F] forSome {type F}) = {
val r: b.R forSome {val x: Int} = b.find(0)
// type mismatch;
// found : F
// required: b.R
// (which expands to) F
b.fill(r)
}
}
Let's look at some code first:
trait Foo[T] {
def fooize(value : T) : Unit
}
object TestFoo {
def testFooForType[T[_] <: Foo[_]](obj : T[Int]) {
obj.fooize(5)
}
}
Why doesn't it typecheck and how can I overcome this difficulty? What I want to do is to test any class that implements the given trait Foo for a particular type T (Int in the example).
T[_] <: Foo[_]
means "T forSome type" extends "Foo forSome type".
It has to allow for cases where the types are different. (T[A] extends Foo[B])
Binding T with Int type has no effect on Foo's type, which is still unbounded.
You can either specify Foo's type:
def testFooForType[T[_] <: Foo[Int]](obj : T[_]) {
obj.fooize(5)
}
or you can make the type relationship explicit:
def testFooForType[T[V] <: Foo[V]](obj : T[Int]) {
obj.fooize(5)
}
What I am trying to do :
trait BasicModel {
type U <: BasicModel
def -(that: U): BasicModel
...
}
class MatrixFactorizationModel(val W: DenseMatrix[Double], val b: Double) extends BasicModel {
type U = MatrixFactorizationModel
def -(that: MatrixFactorizationModel): MatrixFactorizationModel = new MatrixFactorizationModel(W + that.W, b - that.b)
...
}
abstract class SAG [T <: BasicModel#U : ClassTag] {
//type T <: BasicModel#U I also tried like that
def modelDerivative(idx: Index, cstDerivative: Double): T
def compute(): T = {
SumDerivative = SumDerivative - modelDerivative(idx, Hist(idx))
}
}
When compute is call I got this error :
type mismatch;
found : T
required: _129.U where val _129: T
SumDerivative = SumDerivative - modelDerivative(idx, Hist(idx))
I don't understand why this is not working because T is a BasicModel#U. Could someone explain me, and give me an alternative?
EDIT :
I also changed in SAG T <: BasicModel#u in T <: BasicModel and when I use T to change it in T#U :
abstract class SAG [T <: BasicModel : ClassTag] extends Optimizer {
def modelDerivative(idx: Index, cstDerivative: Double): T#U
...
}
But :
type mismatch;
found : T#U
required: _128.U where val _128: T#U
SumDerivative = SumDerivative - modelDerivative(idx, Hist(idx))
It's sort of like the type defined in BasicModel, U, is a member of an instance of BasicModel. It's like the difference between static/instance variables in Java. To match a type definition, you need the exact same type. If it were static, you couldn't override it in the subclass, MatrixFactorizationModel.
T <: BasicModel in SAG represents any T <: BasicModel#U (the parent #U) where that #U could be any subtype of BasicModel. Even though you might happen to have specified MatrixFactorizationModel wherever you instantiated an instance of SAG, the compiler doesn't know what specific T you have in this context, or that it's a subtype of the same U.
You might have better luck with something like trait BasicModel { def [T<:BasicModel]-(that:T):T }. It's a bit more verbose, and can be a real pain if you've got a lot of method signatures to type, but sometimes signatures like that work out better.