I am new to Scala and dont know why i have to do an (unintuitive for me) type cast related to path dependent types in the following code. (I don't like getters, setters nor nulls, they are here to separate operations and disambiguate the source of errors)
// Module A public API
class ModA {
trait A
}
// Module B public API that depends on types defined in Module A
class ModB(val modA: ModA) {
trait B {
def getA: modA.A;
def setA(anA: modA.A);
}
}
// One implementation of Module A
class ModAImpl extends ModA {
class AImpl extends A
}
// One implementation of Module B
class ModBImpl(mod: ModA) extends ModB(mod) {
class BImpl extends B {
private[this] var privA: modA.A = _;
override def getA = privA;
override def setA(anA: modA.A) = privA = anA;
}
}
object Main {
def main(args: Array[String]): Unit = {
// wiring the modules
val modAImpl = new ModAImpl;
val modBImpl = new ModBImpl(modAImpl);
// wiring objects
val a = new modAImpl.AImpl;
val b = new modBImpl.BImpl;
b.setA(a); //don't compile and complain: type mismatch; found: modAImpl.A required: modBImpl.modA.A
//i have to do this horrible and coutnerintuitive cast to workaround it
b.setA(a.asInstanceOf[modBImpl.modA.A]);
var someA: modAImpl.A = null;
someA = b.getA; // don't compile with same reason
someA = b.getA.asInstanceOf[modAImpl.A]; // horrible cast to workaround
println(a == b.getA); // however this prints true
println(a eq b.getA); // this prints true too
}
}
I have read about singleton types to inform the compiler when two types are the same, but I don't know how to apply this here. Thanks in advance.
Let's start by simplifying your code ridding out the unnecessary complexity.
class Aout {
class Ain
}
class Bout(val link: Aout) {
class Bin(val field: link.Ain)
}
object Main {
def main(args: Array[String]): Unit = {
// wiring outer object
val aout: Aout = new Aout;
val bout: Bout = new Bout(aout);
// wiring inner object
val ain: aout.Ain = new aout.Ain;
val bin: bout.Bin = new bout.Bin(ain); //don't compile and complain: type mismatch; found: aout.Ain required: bout.link.Ain
}
}
Answer:
The compiler complains with a type mismatch error because he compares the paths of the two declared types involved in the assignment, and they are different.
The compiler is not intelligent enough to notice that at that point aout eq bout.link
. You have to tell him.
So, replacing the line
val ain: aout.Ain = new aout.Ain;
with
val ain: bout.link.Ain = new bout.link.Ain;
solves the path-dependent type mismatch.
Anyway, correcting the type's path in your original code is not enough because there is also an inheritance problem.
One solution to that is to make the class ModBImpl
know the ModAImpl
class like this:
class ModA {
trait A
}
class ModB[M <: ModA](val modA: M) { // type parameter added
trait B {
def getA: modA.A;
def setA(anA: modA.A);
}
}
class ModAImpl extends ModA {
class AImpl extends A
}
class ModBImpl(mod: ModAImpl) extends ModB(mod) { // changed type of `mod` parameter from `ModA` to `ModAImpl`
class BImpl extends B {
private[this] var privA: modA.A = _;
override def getA: modA.A = privA;
override def setA(anA: modA.A): Unit = privA = anA;
}
}
object Main {
def main(args: Array[String]): Unit = {
val modAImpl = new ModAImpl;
val modBImpl = new ModBImpl(modAImpl);
val a: modBImpl.modA.AImpl = new modBImpl.modA.AImpl; // changed the path of the type
val b: modBImpl.BImpl = new modBImpl.BImpl;
b.setA(a); // here IntellijIde complains with a type mismatch error, but the ScalaIDE (eclipse) and the scala compiler work fine.
}
}
If the rules of your business don't allow that the ModBImpl
class has knowledge of the ModAImpl
class, tell me so we can find another solution.