9

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 }
Vilius Normantas
  • 3,708
  • 6
  • 25
  • 38
  • Scripts have less perspective. Try putting it all into some other object so it's clear that everything has to be interpreted together. – Rex Kerr Feb 12 '11 at 13:42

1 Answers1

10

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.

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • Is your solution better than my EDIT2? – Vilius Normantas Feb 12 '11 at 13:55
  • What do you mean by "better"? My version requires less typing if you're doing this with many objects, but yours will be slightly (though probably irrelevantly) faster and is more compact if you only need two items. – Rex Kerr Feb 12 '11 at 14:16