11

Suppose I have a stupid little case class like so:

case class Foo(name: String, other: Foo)

How can I define a and b immutably such that a.other is b, and b.other is a? Does scala provide some way to "tie the knot"? I'd like to do something like this:

val (a, b): (Foo, Foo) = (Foo("a", b), Foo("b", a)) // Doesn't work.

Possibilities

In Haskell I would do this:

data Foo = Foo { name :: String, other :: Foo }

a = Foo "a" b
b = Foo "b" a

Where the bindings to a and b are contained in the same let expression, or at the top level.

Or, without abusing Haskell's automagical letrec capabilities:

(a, b) = fix (\ ~(a', b') -> Foo "a" b', Foo "b" a')

Note the lazy pattern, ~(a', b'), that's important.

mergeconflict
  • 7,861
  • 31
  • 63
Dan Burton
  • 51,332
  • 25
  • 109
  • 190
  • 3
    I wonder how many search engines will now start finding this questions for "wedding"... –  Jun 06 '12 at 00:39
  • 2
    This question is more or less a duplicate of http://stackoverflow.com/questions/7507965/instantiating-immutable-paired-objects. Also, if it were possible with case classes the `toString` would recurse forever – Luigi Plinge Jun 06 '12 at 00:44
  • @LuigiPlinge that solution infects the definition of the class itself. I'd like to see a solution where `Foo` is unchaged. `toString` would indeed recurse forever. – Dan Burton Jun 06 '12 at 00:58

1 Answers1

14

You want Foo to be unchanged, but laziness in Scala is on the declaration site. It is impossible for Foo to be non-strict without changing it, and the pattern indicated in Haskell only works because Foo, there, is non-strict (that is, Foo "a" b doesn't evaluate b immediately).

Otherwise the solution is pretty much the same, allowing for the hoops necessary to get everything non-strict:

class Foo(name: String, other0: => Foo) { // Cannot be case class, because that mandates strictness
  lazy val other = other0 // otherwise Scala will always reevaluate
}
object Foo {
  def apply(name: String, other: => Foo) = new Foo(name, other)
}

val (a: Foo, b: Foo) = (Foo("a", b), Foo("b", a))
Daniel C. Sobral
  • 284,820
  • 82
  • 479
  • 670
  • 1
    Ah you're right. Defining `data Foo = Foo { name :: !String, other :: !Foo }` causes the Haskell solutions to not work. – Dan Burton Jun 06 '12 at 04:33
  • Thanks for the comment "otherwise Scala will always reevaluate". That's good to know! – Madoc Jun 06 '12 at 11:20