5

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.

2 Answers2

6

You can stick a type parameter on the ModB types:

class ModA { trait A }

class ModB[AA](val modA: ModA { type A = AA }) {
  trait B {
    def getA: AA
    def setA(anA: AA)
  }
}

class ModAImpl extends ModA { class AImpl extends A }

class ModBImpl[AA](
  mod: ModA { type A = AA }) extends ModB(mod) {
  class BImpl extends B {
    private[this] var privA: AA = _
    override def getA = privA
    override def setA(anA: AA) = privA = anA
  }
}

And the type inference all works out as desired:

scala> val modAImpl = new ModAImpl
modAImpl: ModAImpl = ModAImpl@7139edf6

scala> val modBImpl = new ModBImpl(modAImpl)
modBImpl: ModBImpl[modAImpl.A] = ModBImpl@1dd7b098

scala> val a = new modAImpl.AImpl
a: modAImpl.AImpl = ModAImpl$AImpl@4cbde97a

scala> val b = new modBImpl.BImpl
b: modBImpl.BImpl = ModBImpl$BImpl@63dfafd6

scala> b.setA(a)
Travis Brown
  • 135,682
  • 12
  • 352
  • 654
  • Very thanks for answering Travis, but your solution only works inside the class scope, inside a method scope (like the example i had originally exposed) it does not work. the line: `val modBImpl = new ModBImpl(modAImpl);` that previously compile, now complains with: type mismatch; found : ModAImpl required: M forSome { type M <: :="" a="this.A}" and="" complains="" found="" line="" mismatch="" moda="" modaimpl.a="" now="" required:="" the="" this.a="" type="" with:=""> – Guillermo Pollitzer Nov 11 '12 at 14:22
  • You can provide the type parameter explicitly in that case (`val modBImpl = new ModBImpl[modAImpl.A](modAImpl)`). Which isn't satisfying, but it works, and it's better than casting. – Travis Brown Nov 11 '12 at 16:15
  • Sorry Travis, but the line of code you just wrote doesn't compile. Despite that, please assume that trait ModA has lots of members, not just A. I need all the members of ModA available on ModB. – Guillermo Pollitzer Nov 11 '12 at 17:03
0

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.

Readren
  • 780
  • 1
  • 8
  • 17