Ошибка компиляции класса Scala Case с псевдонимами внутренних типов? - PullRequest
4 голосов
/ 28 ноября 2009

Как использовать сопоставление классов дел с псевдонимами? Это работает, когда я вытаскиваю CB и т. Д. Из контейнера.

class DoStuff[TKey](
  val c : Container[TKey]#CB
)
{
  type CB = Container[TKey]#CB
  type C1 = Container[TKey]#C1
  type C2 = Container[TKey]#C2

  c match {
    case C1(e1) => e1  //   - not found: value e1   - not found: value C1
    case C2(e2) => e2 //    - not found: value e2   - not found: value C2
  }
}

trait Container[TKey]
{
    abstract trait CB
    case class C1(val e : AnyRef) extends CB
    case class C2(val e : AnyRef) extends CB
}

Спасибо! * * 1004

1 Ответ

8 голосов
/ 28 ноября 2009

Правильно ... Внутренние классы в Scala немного неудобны. Давайте попробуем простой пример, прежде чем я покажу вам переписанную версию предоставленного вами кода.

case class Foo(x: Int) {
  case class Bar(y: String) 
}

Теперь рассмотрим следующий фрагмент кода:

val x = new Foo(1)
val y = new Foo(2)

val a = new x.Bar("one")
val b = new y.Bar("two") 

Наиболее общим типом a и b является Foo#Bar, что означает внутренний класс Bar с любым внешним объектом типа Foo. Но мы могли бы более конкретно сказать, что тип a равен x.Bar, а тип b равен y.Bar, что означает, что a является экземпляром внутреннего класса Bar с внешним объект x, аналогичный для b.

В действительности вы можете увидеть, что типы различаются, вызвав typeOf(a) и typeOf(b), где typeOf - это служебный метод, определенный как таковой. (он просто дает тип своего аргумента довольно хорошим выводом типа и немного использует Manifest с)

def typeOf[T](x: T)(implicit m: scala.reflect.Manifest[T]) = m.toString

Поскольку внутренний объект содержит ссылку на окружающий его объект, вы не можете создать экземпляр внутреннего объекта, не указав каким-либо образом его внешний объект. Следовательно, вы можете вызвать new x.Bar("one"), но не можете вызвать new Foo#Bar("?") - так как во втором случае вы не указали, что является внутренним объектом для нового объекта, который вы пытаетесь построить.

Итак, давайте вернемся к вашему фрагменту кода. Когда вы сопоставляете шаблон, вы на самом деле вызываете конструктор - при вызове C1(e1). Поскольку C1 является псевдонимом для Container[TKey]#C1 , вы попытались вызвать конструктор внутреннего класса, не указав его внешний объект, который завершается неудачей по причинам, изложенным выше. Я бы написал код следующим образом:

trait Container[TKey] {
    abstract trait CB
    case class C1(val e : AnyRef) extends CB
    case class C2(val e : AnyRef) extends CB
}

class DoStuff[TKey] (val c: Container[TKey], val element: Container[TKey]#CB) {
  element match {
    case c.C1(e1) => Some(e1)
    case c.C2(e2) => Some(e2)
    case _        => None
  }
}

Теперь он компилируется и, надеюсь, он делает то, что вы хотите. Но примите это с большой осторожностью! Из-за стирания типа Scala не может гарантировать, что element на самом деле имеет тип c.CB или типа d.CB, где CB в случае c и d совпадают.

Рассмотрим этот пример:

def matcher(arg: Foo#Bar) = {
  arg match {
    case x.Bar(n) => println("x");
    case y.Bar(n) => println("y");
  }
}

, где x и y такие же, как и раньше. Попробуйте выполнить следующее:

matcher(a)
matcher(b) 

Они оба печатают x!

Поэтому я бы переписал код, чтобы явно иметь элемент в контейнере:

trait Container[TKey] {
    abstract trait CB
    case class C1(val e : AnyRef) extends CB
    case class C2(val e : AnyRef) extends CB
    val element: CB
}

class DoStuff[TKey](val c: Container[TKey]) {
  c.element match {
    case c.C1(e1) => Some(e1)
    case c.C2(e2) => Some(e2)
    case _        => None
  }
}

Надеюсь, это поможет:)

- Flaviu Cipcigan

...