scala: круговая ссылка при создании объекта?

Я случайно столкнулся с такой ситуацией (пример упрощен, чтобы выделить проблему):

abstract class Element(val other: Element)

case object First extends Element(Second)
case object Second extends Element(First)

object Main {
  def main(arguments: Array[String]) {
    val e1 = First
    val e2 = Second
    println("e1: "+e1+"   e1.other: "+e1.other)
    println("e2: "+e2+"   e2.other: "+e2.other)
  }
}

Кто-нибудь хотел бы угадать вывод?:-)

e1: First   e1.other: Second
e2: Second   e2.other: null

Вывод имеет смысл. По-видимому, на момент создания Второго объекта Первый еще не существует, поэтому null назначен. Проблема в том... Это так неправильно! Мне понадобилось несколько часов, чтобы отследить это. Разве компилятор не должен что-то говорить об этом? Интересно, что когда я пытался запустить вещь как скрипт Scala (тот же код, минус object Main а также def main строки и закрытие }s), я получил бесконечную последовательность (не совсем бесконечную - в какой-то момент список останавливается, я полагаю, из-за некоторого ограничения глубины следов исключений или чего-то подобного) исключений, подобных этому:

vilius@blackone:~$ scala 1.scala
...
at Main$$anon$1.Main$$anon$$Second(1.scala:4)
at Main$$anon$1$First$.<init>(1.scala:3)
at Main$$anon$1.Main$$anon$$First(1.scala:3)
at Main$$anon$1$Second$.<init>(1.scala:4)
at Main$$anon$1.Main$$anon$$Second(1.scala:4)
at Main$$anon$1$First$.<init>(1.scala:3)
...

Я хотел бы получить что-то хотя бы информативное во время выполнения...

Хорошо. Я закончил свою напыщенную речь. Теперь, я думаю, мне следует кое-что спросить.:) Итак, не могли бы вы порекомендовать какой-нибудь красивый дизайн для объектов, указывающих один на другой? Кстати, в моей реальной ситуации есть несколько объектов, указывающих на следующий и предыдущий экземпляры по кругу (последний указывает на первый и наоборот).

Использование Scala 2.8.1-final

РЕДАКТИРОВАТЬ: я нашел решение для моей главной проблемы:

abstract class Element {
  val other: Element
}
case object First extends Element {
  val other = Second
}
case object Second extends Element {
  val other = First
}

Кажется, это работает в скомпилированной версии (но не как скрипт Scala!). Может ли кто-нибудь пролить свет на то, что здесь происходит?

EDIT2: это работает как скрипт (то же самое, просто используя defs):

abstract class Element { def other: Element }
case object First extends Element { def other = Second }
case object Second extends Element { def other = First }

1 ответ

Решение

Обычный способ выглядит следующим образом (измененное вложение, чтобы вы могли вставить его в REPL):

object Main{
  abstract class Element(other0: => Element) {
    lazy val other = other0
  }

  case object First extends Element(Second)
  case object Second extends Element(First)

  def main(arguments: Array[String]) {
    val e1 = First
    val e2 = Second
    println("e1: "+e1+"   e1.other: "+e1.other)
    println("e2: "+e2+"   e2.other: "+e2.other)
  }
}

То есть, взять параметр по имени и вставить его в ленивый val для дальнейшего использования.


Редактировать: исправление, которое вы нашли, работает, потому что сами объекты ленивы в том, что вы можете ссылаться на них, но они не создаются, пока вы их не используете. Таким образом, один объект может указывать на другой, не требуя, чтобы другой был уже инициализирован.

Другие вопросы по тегам