2

Suppose I have these two classes:

class Person (val name: String, val surname: String)

class Family (val nameFamily: String, val members: Set[Person])

Now in the main method instantiate two classes as follows:

val F1 = new Family ("Red", Set(P1, P2))
val P1 = new Person ("John", "Smith")
val P2 = new Person ("Luis", "Smith")

The main method allows me to enter the family members before their instantiation. I want this for my model. But if I enter the members before their creation, when I go to write:

println(F1.members)

I returns a Set (null).

If you write in the main people first, like this:

val P1 = new Person ("John", "Smith")
val P2 = new Person ("Luis", "Smith")  
val F1 = new Family ("Red", Set(P1, P2))

I have no problem.

But I want to write instances in any order and in the end run a validation of family.

I can solve this problem. I mean, I can initialize my fields with instances created later.

Excuse me for the bad English translation.

UPLOAD #1

I have implemented a domain in Scala, I create instances of the domain with a DSL. My DSL allows me to instantiate classes in mixed order. For example, I create a Component, then add to this Component some Type. Then I create the Type I have added to Component. In the main method I can do it. As the last statement of the main I put validation. When starting validation, Type in the Component does not find anything, because these are instantiated later. This problem can be resolved only in the main with lazy? Or is there a solution at the domain level.

user1826663
  • 357
  • 1
  • 3
  • 15
  • I guess you can try to pass `by-name` constructor arguments, as in `A(byname: => B)`, to all your domain objects, initializing inner `lazy` members. But it's hard to say in a generic fashion, it depends on how your domain and your program is structured. I seems like creating an order independent DSL is not an easy task, if it's at all possible! – pagoda_5b Dec 07 '12 at 15:05

2 Answers2

7

You can use lazy val:

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

case class Person (val name: String, val surname: String)
case class Family (val nameFamily: String, val members: Set[Person])

lazy val f1 = new Family ("Red", Set(p1, p2))
lazy val p1 = new Person ("John", "Smith")
lazy val p2 = new Person ("Luis", "Smith")

// Exiting paste mode, now interpreting.

defined class Person
defined class Family
f1: Family = <lazy>
p1: Person = <lazy>
p2: Person = <lazy>

scala> f1
res0: Family = Family(Red,Set(Person(John,Smith), Person(Luis,Smith)))

If you want to set some value somewhere after creation of object only once you should use Promise from scala 2.10:

import concurrent.{Promise, Future}

case class Person (val name: String, val surname: String)
class Family (val nameFamily: String) {
  private val membersPromice = Promise[Set[Person]]
  def setMembers(m: Set[Person]) { membersPromice.success(m) }
  val membersFuture = membersPromice.future
  def members = membersFuture.value.map(_.get)
  override def toString() = "Family(" + nameFamily + ", " + members.getOrElse("<future>") + ")"
}

Usage:

scala> val f = new Family("red")
f: Family = Family(red, <future>)

scala> val (p1, p2) = (Person("John", "Smith"), Person("Luis", "Smith"))
p1: Person = Person(John,Smith)
p2: Person = Person(Luis,Smith)

scala> f.setMembers(Set(p1, p2))

scala> f
res1: Family = Family(red, Set(Person(John,Smith), Person(Luis,Smith)))

scala> f.setMembers(Set(p1, p2))
java.lang.IllegalStateException: Promise already completed.

You can use type-safe builder pattern:

case class Person (val name: String, val surname: String)
case class Family (val nameFamily: String, val members: Set[Person])

sealed abstract class TBool
class TTrue extends TBool
class TFalse extends TBool
class FamilyBuilder[WithName <: TBool, WithMembers <: TBool](val nameFamily: Option[String], val members: Option[Set[Person]]) {
  def withName(nameFamily: String)(implicit i: WithName =:= TFalse) = new  FamilyBuilder[TTrue, WithMembers](Some(nameFamily), members)
  def withMembers(members: Set[Person])(implicit i: WithMembers =:= TFalse) = new  FamilyBuilder[WithName, TTrue](nameFamily, Some(members))
  def create()(implicit i1: WithName =:= TTrue, i2: WithMembers =:= TTrue) = Family(nameFamily.get, members.get)
}
object FamilyBuilder {
  def apply() = new FamilyBuilder[TFalse, TFalse](None, None)
}

Usage:

scala> FamilyBuilder() withName "Red" withMembers Set(p1, p2) create()
res1: Family = Family(Red,Set(Person(John,Smith), Person(Luis,Smith)))

scala> FamilyBuilder() withMembers Set(p1, p2) withName "Red" create()
res2: Family = Family(Red,Set(Person(John,Smith), Person(Luis,Smith)))

scala> FamilyBuilder() withName "Red" create()
<console>:1: error: Cannot prove that TFalse =:= TTrue.
              FamilyBuilder() withName "Red" create()
                                             ^
senia
  • 37,745
  • 4
  • 88
  • 129
  • I can only use the variables lazy in the main. There is a solution to be applied in the model of domain, on the declaration of the class. – user1826663 Dec 07 '12 at 11:15
  • Hi, thanks for the answer, but it is not what I'm looking. I have updated my problem. – user1826663 Dec 07 '12 at 12:24
  • Okay, let's try to reach your goal with builder. – senia Dec 07 '12 at 12:41
  • Useful answer, builder link is dead though and not enough explanation of it based solely off your answer. – JMess Sep 18 '17 at 16:10
  • @JMess see update. You could also take a look at these 2 answers for additional examples: [1](https://stackoverflow.com/a/31400721/406435), [2](https://stackoverflow.com/a/17884093/406435) – senia Sep 19 '17 at 08:26
1

You just need to make the persons variables lazy

val F1 = new Family ("Red", Set(P1, P2))
lazy val P1 = new Person ("John", "Smith")
lazy val P2 = new Person ("Luis", "Smith")

scala> println(F1.members)
Set($line1.$read$$iw$$iw$Person@185623a7, $line1.$read$$iw$$iw$Person@3f3eb56c)

Edited

You can define the Family members as lazy in the class itself

class Family(val nameFamily: String, memberSet: => Set[Person]) {
    lazy val members= memberSet
}

This way the members attribute is evaluated only on demand

pagoda_5b
  • 7,333
  • 1
  • 27
  • 40