Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
484 views
in Technique[技术] by (71.8m points)

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 }
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...