If you really want to build graphs of immutable objects with circular dependencies, you have to declare opposite
as def
, and (preferably) throw one more lazy val into the mix:
abstract class Relationship(val name: String) {
def opposite: Relationship
}
object Relationship {
/** Factory method */
def apply(nameA: String, nameB: String): Relationship = {
lazy val x: Relationship = new Relationship(nameA) {
lazy val opposite = new Relationship(nameB) {
def opposite = x
}
}
x
}
/** Extractor */
def unapply(r: Relationship): Option[(String, Relationship)] =
Some((r.name, r.opposite))
}
val ns = Relationship("North", "South")
println(ns.name)
println(ns.opposite.name)
println(ns.opposite.opposite.name)
println(ns.opposite.opposite.opposite.name)
You can quickly convince yourself that nothing bad happens if you run a few million rounds on this circle of circular dependencies:
// just to demonstrate that it doesn't blow up in any way if you
// call it hundred million times:
// Should be "North"
println((1 to 100000000).foldLeft(ns)((r, _) => r.opposite).name)
It indeed prints "North". It doesn work with case classes, but you can always add your own extractors, so this works:
val Relationship(x, op) = ns
val Relationship(y, original) = op
println(s"Extracted x = $x y = $y")
It prints "North" and "South" for x
and y
.
However, the more obvious thing to do would be to just save both components of a relation, and add opposite
as a method that constructs the opposite pair.
case class Rel(a: String, b: String) {
def opposite: Rel = Rel(b, a)
}
Actually, this is already implemented in the standard library:
scala> val rel = ("North", "South")
rel: (String, String) = (North,South)
scala> rel.swap
res0: (String, String) = (South,North)