Правильно ... Внутренние классы в 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