Ошибка компиляции сопоставления класса Case Scala с внутренними типами с псевдонимами?
Вопрос
Как мне использовать сопоставление класса case с типами с псевдонимами?Это работает, когда я вытаскиваю CB etc из контейнера.
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
}
Спасибо!
Решение
Правильно...Внутренние классы в 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
s)
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