3

With these classes as written, is there any possible way to create a boyfriend/girlfriend pair? That is, a boy with a girlfriend and the boy is also her boyfriend.

abstract class Person(val name: String)

case class Girl(name2: String, val boyfriend: Boy) extends Person(name2)

case class Boy(name2: String, val girlfriend: Girl) extends Person(name2)


object Run extends App {
    val alice: Girl = Girl("alice", Boy("Bob",alice))

    // alice.boyfriend.girlfriend is null, not correct
}
  • 2
    An immutable object represents the state of an entity at one point in time. But to model the relationship between persons, you have to have a way to refer to a person even if it changes over time. So you need some kind of handle or id that stays the same even if the person changes. It is very important to distinguish between an entity and the state of an entity at some point in time. See this excellent talk that illustrates the point very well: http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey – Rüdiger Klaehn Oct 23 '13 at 08:14

1 Answers1

9

It is impossible to make self-referential immutable structures without lazy evaluation. However, lazy evaluation in Scala (represented with lazy vals and call-by-name parameters) is not so pervasive as, say, in Haskell, and it has its limitations. Your code can be rewritten like this:

Welcome to Scala version 2.10.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

scala> :paste
// Entering paste mode (ctrl-D to finish)

abstract class Person(val name: String)

class Girl(val name2: String, _boyfriend: => Boy) extends Person(name2) {
    lazy val boyfriend = _boyfriend
}

class Boy(val name2: String, _girlfriend: => Girl) extends Person(name2) {
    lazy val girlfriend = _girlfriend
}


object Run {
    val alice: Girl = new Girl("alice", new Boy("Bob", alice))
}

// Exiting paste mode, now interpreting.

defined class Person
defined class Girl
defined class Boy
defined module Run

scala> Run.alice.name
res0: String = alice

scala> Run.alice.boyfriend.name
res1: String = Bob

scala> Run.alice.boyfriend.girlfriend.name
res2: String = alice

scala>

You see, no case classes here - you cannot make case class parameters call-by-name since they are exposed as vals, and it does not make sense for vals to have call-by-name type. Also I had to create additional lazy val field so that call-by-name parameter won't be evaluated too early.

It is possible that this code can be simplified, but it is the simplest way to achieve what you want I can think of.

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • I realy enjoy your solution, but I have tried without `Run` object and got `Forward reference extends over definition of value alice` in Scala 2.10.2. Was solved by making `lazy val alice`. Why this occurs? – kisileno Oct 23 '13 at 20:29
  • @brainless, what do you mean, "Without `Run` object"? See [this gist](https://gist.github.com/dpx-infinity/7132006), there is no objects but the definition still works. – Vladimir Matveev Oct 24 '13 at 05:53
  • Vladimir Matveev, I tried this in REPL and all works correctly. But if I insert it to existing scala file and compile, I have an error. Maybe there is a problem of my env, never mind. Thanks! – kisileno Oct 24 '13 at 10:09