Как избежать ужасного приведения типов, работающих с типами, зависящими от пути?

Я новичок в Scala и не знаю, почему я должен выполнять (неинтуитивное для меня) приведение типов, связанное с типами, зависящими от пути, в следующем коде. (Мне не нравятся геттеры, сеттеры или нули, они здесь для разделения операций и устранения неоднозначности источника ошибок)

// 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
  }
} 

Я читал о одноэлементных типах, чтобы сообщить компилятору, когда два типа совпадают, но я не знаю, как это применить здесь. Заранее спасибо.


person Guillermo Pollitzer    schedule 11.11.2012    source источник


Ответы (2)


Давайте начнем с упрощения вашего кода, избавившись от ненужной сложности.

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
    }
}

Ответ:

Компилятор жалуется на ошибку несоответствия типов, потому что он сравнивает пути двух объявленных типов, участвующих в присваивании, а они разные. Компилятор недостаточно умен, чтобы заметить, что в этот момент aout eq bout.link. Ты должен сказать ему. Итак, замена строки

val ain: aout.Ain = new aout.Ain;

с участием

val ain: bout.link.Ain = new bout.link.Ain;

устраняет несоответствие типов, зависящее от пути.

В любом случае, исправления пути к типу в исходном коде недостаточно, потому что существует также проблема наследования. Одним из решений этого является сделать так, чтобы класс ModBImpl знал класс ModAImpl следующим образом:

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.
    }
}

Если правила вашего бизнеса не позволяют, чтобы класс ModBImpl знал класс ModAImpl, скажите мне, чтобы мы могли найти другое решение.

person Readren    schedule 22.09.2019

Вы можете прикрепить параметр типа к типам ModB:

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
  }
}

И вывод типа работает так, как хотелось бы:

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)
person Travis Brown    schedule 11.11.2012
comment
Большое спасибо за ответ Трэвису, но ваше решение работает только внутри области класса, внутри области действия метода (например, в примере, который я изначально представил), оно не работает. строка: val modBImpl = new ModBImpl(modAImpl);, которая ранее компилировалась, теперь жалуется на: несоответствие типов; найдено: требуется ModAImpl: M forSome { type M ‹: ModA {type A = this.A} } и строка b.set(a) теперь жалуется на: несоответствие типов; найдено: modAImpl.A требуется: this.A - person Guillermo Pollitzer; 11.11.2012
comment
В этом случае вы можете указать параметр типа явно (val modBImpl = new ModBImpl[modAImpl.A](modAImpl)). Что не приносит удовлетворения, но это работает, и это лучше, чем кастинг. - person Travis Brown; 11.11.2012
comment
Извини, Трэвис, но строка кода, которую ты только что написал, не компилируется. Несмотря на это, предположим, что у черты ModA много членов, а не только A. Мне нужны все члены ModA, доступные в ModB. - person Guillermo Pollitzer; 11.11.2012