Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

scala: circular reference while creating object?

I accidentally ran into a situation like this (the example is simplified to isolate the problem):

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

Anyone would like to guess the output? :-)

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

The output makes kind of sense. Apparently at the time the Second object is created, the First one does not yet exist, therefore null is assigned. The problem is... It's so wrong! It took me a couple of hours to track this one down. Shouldn't the compiler tell something about this? Interestingly, when I tried to run the thing as a Scala script (the same code, minus object Main and def main lines, and closing }s), I got an infinite sequence (not really infinite - at some point the list stops, I guess due to some limitation on the depth of Exception traces, or something) of exceptions like this:

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

I'd love to get something at least as informative during runtime...

Ok. I finished my rant. Now I guess I should ask something. :) So, could you recommend any nice design for case objects pointing one to another? By the way, in my real situation there are several objects pointing to the next and previous instances in circular way (the last one points to the first one and vice versa).

Using Scala 2.8.1-final

EDIT: I found a solution for my main problem:

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

This seems to work in compiled version (but not as a Scala script!). Could anyone shed some light on what's going on here?

EDIT2: This works as a script (the same thing, just using defs):

abstract class Element { def other: Element }
case object First extends Element { def other = Second }
case object Second extends Element { def other = First }
like image 979
Vilius Normantas Avatar asked Feb 12 '11 13:02

Vilius Normantas


1 Answers

The usual way is like this (changed nesting so you can paste it into the 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)
  }
}

That is, take a by-name parameter and stick it into a lazy val for future reference.


Edit: The fix you found works because objects are themselves lazy in that you can refer to them but they don't get created until you use them. Thus, one object is free to point itself at the other without requiring that the other one has been initialized already.

like image 164
Rex Kerr Avatar answered Nov 13 '22 05:11

Rex Kerr