3

How in Scala I can define local variable in primary constructor?

I need to solve this exercise from Scala for the impatient book:

Write a class Person with a primary constructor that accepts a string containing a first name, a space, and a last name, such as new Person("Fred Smith"). Supply read-only properties firstName and lastName. Should the primary constructor parameter be a var, a val, or a plain parameter? Why?

And for now my solution looks like this:

class Person(firstLast: String) {
  private[this] val firstLastAsArr = firstLast.trim.split(" ")

  val firstName = firstLastAsArr (0)
  val lastName = firstLastAsArr (1)
}

How I can restrict firstLastAsArr variable visibility to primary constructor scope (now it have class scope)?

giampaolo
  • 6,906
  • 5
  • 45
  • 73
WelcomeTo
  • 19,843
  • 53
  • 170
  • 286
  • possible duplicate of [How do you define a local var/val in the primary constructor in Scala?](http://stackoverflow.com/questions/1118669/how-do-you-define-a-local-var-val-in-the-primary-constructor-in-scala) – Jukka Suomela Jun 10 '14 at 13:46

5 Answers5

5

One solution is to initialize firstName and lastName at once, thereby allowing to turn firstLastAsArr into a local temporary value inside your initialization block:

class Person(firstLast: String) {
  val (firstName, lastName) = {
    val firstLastAsArr = firstLast.trim.split(" ")
    (firstLastAsArr(0), firstLastAsArr(1))
  }
}
Régis Jean-Gilles
  • 32,541
  • 5
  • 83
  • 97
4

It is not a general answer, but in this particular way you may write:

  val Array(firstName, lastName) = firstLast.trim.split(" ")
om-nom-nom
  • 62,329
  • 13
  • 183
  • 228
2

You don't strictly need the intermediate variable:

class Person(firstLast: String) {
  val (firstName, lastName) =
    firstLast.trim.split(" ") match {case Array(first, last) => (first, last)}
}

However, if your transformation from firstLast to firstName and lastName grows a big longer, for example, because you check that there is exactly one first and one last name, then I would encapsulate the whole splitting-business in a dedicated method:

class Person(firstLast: String) {
  val (firstName, lastName) = split(firstLast)

  private def split(firstLast: String): (String, String) = {
    val firstLastAsArr = firstLast.trim.split(" ")
    ...
    (first, last)
  }
}
Malte Schwerhoff
  • 12,684
  • 4
  • 41
  • 71
1

Pattern matching in constructor works just fine, but you should consider moving such logic from constructor to factory method:

case class Person(firstName: String, lastName: String)
object Person{
  def apply(firstLast: String) = {
    val firstLastAsArr = firstLast.trim.split(" ")
    new Person(firstLastAsArr(0), firstLastAsArr(1))
  }
}

val p = Person("My Title")
senia
  • 37,745
  • 4
  • 88
  • 129
  • 1
    @pagoda_5b: this is really not a correct answer in this case, but I can't add formated code sample to comment. I think the one who need to initialize 2 fields from 1 constructor parameter should use a factory method. SO is not about homework. – senia Jun 20 '13 at 12:58
  • +1 for the factory method solution -- I'd also go for this pattern, as it avoids creating temporary tuples. – axel22 Jun 20 '13 at 13:08
0

Pattern maching in primary constructor works well

  class Person(_fullName:String) {
    val (firstName, lastName) =  _fullName.split(" ") match {
      case Array(x:String, y:String, _*) => (x,y)
      case _ => (null,null)
    }
  }

See my github for full answer https://github.com/BasileDuPlessis/scala-for-the-impatient/blob/master/src/main/scala/com/basile/scala/ch05/Ex07.scala