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 def
s):
abstract class Element { def other: Element }
case object First extends Element { def other = Second }
case object Second extends Element { def other = First }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With