0

simple case class:

case class country(name: String, townPopulation: Map[String,Int])

with simple example:

scala> val germany = country("Germany",Map("Berlin" -> 100000, "Saale" -> 4000))
germany: country = country(Germany,Map(Berlin -> 100000, Saale -> 4000))

scala> germany.townPopulation("Berlin")
res77: Int = 100000

scala> germany.townPopulation("blergh")
java.util.NoSuchElementException: key not found: blergh
  at scala.collection.MapLike$class.default(MapLike.scala:228)
  at scala.collection.AbstractMap.default(Map.scala:59)
  at scala.collection.MapLike$class.apply(MapLike.scala:141)
  at scala.collection.AbstractMap.apply(Map.scala:59)
  ... 42 elided

I would like to return 0 for towns that dont exist, this can be done when declaring a val:

scala> val germany = country("Germany",Map("Berlin" -> 100000, "Saale" -> 4000).withDefaultValue(0))
germany: country = country(Germany,Map(Berlin -> 100000, Saale -> 4000))

scala> germany.townPopulation("fdhjkjhkhjdfg")
res79: Int = 0

but I can not figure out how to do it in one place, at least not when it is a case class, I would like something simple as the following, but I am obviously doing it wrong:

scala> case class country(name: String, townPopulation: Map[String,Int].withDefaultValue(0))
<console>:1: error: ')' expected but '.' found.
case class country(name: String, townPopulation: Map[String,Int].withDefaultValue(0))
                                                                ^
<console>:1: error: ';' expected but ')' found.
case class country(name: String, townPopulation: Map[String,Int].withDefaultValue(0))

Is there a short simple path to a solution that has 0 as defaultValue always?

FelixHJ
  • 1,071
  • 3
  • 12
  • 26
  • Just reading in http://stackoverflow.com/a/37588616/409976's answer that the value == population, there seems to be a difference between "having a population, i.e. a key exists for the given city/town" and not existing in the map at all. With `0`, such distinction is lost. Perhaps `Option[Int]` is not a better fit for the value in the map's key-value pairs? – Kevin Meredith Jun 02 '16 at 13:57

3 Answers3

2

I see few possible ways:

  • add auxiliary method which encapsulate logic of default value

    def population(town : String) : Int = townPopulation.getOrElse(town, 0)

  • add method to companion object with same purpose

    def withDefault(name: String, townPopulation: Map[String, Int]) : country = country(name, townPopulation.withDefaultValue(0))

Sergii Lagutin
  • 10,561
  • 1
  • 34
  • 43
1

Use map.get(), which returns an Option:

println germany.townPopulation.get("blergh").getOrElse(0)
// or, more concisely:
println germany.townPopulation.getOrElse("blergh", 0)

Ah, on re-reading your question, you want to hard-code the default value in the case class. I guess you'll have to mess with the apply() method.

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
0
val germany = country("Germany",
                      Map("Berlin" -> 100000, "Saale" -> 4000)
                        .withDefaultValue(0))

Edit (after OP's answer):

My bad! Should have read more thoroughly your question.

As stated in this SO question: You do not have the option of changing the way the default constructor stores its parameters (e.g. by modifying the parameters before they are stored as vals) […].

An alternate solution would be to declare a regular class along with its companion object:

class Country(val name: String, val townPopulation: Map[String, Int])
case object Country {
  def apply(name: String, townPopulation: Map[String, Int]) =
    new Country(name, townPopulation.withDefaultValue(0))
}

This would allow you to declare countries using the nice syntax:

val germany = Country("Germany", Map("Berlin" -> 100000, "Saale" -> 4000))
assert(germany.townPopulation("Berlin") == 100000)
assert(germany.townPopulation("Blergh") == 0)

But note that, as it is not a case class you won't get the usual case class perks such as:

// Compiler will give you
//    "object Country is not a case class,
//     nor does it have an unapply/unapplySeq member"
//germany match {
//  case Country(a, b) => println("It won't compile! Not a case class")
//}

Depending on your use case, you could go the long hard road and implement methods unapply and unapplySeq to retrieve such behavior if needed!

Community
  • 1
  • 1
galaux
  • 375
  • 2
  • 14
  • I have that one in there, it does it, but you have to use withDefaultValue everytime, I would prefer to just declare directly it in the case class and never have to worry about it later – FelixHJ Jun 02 '16 at 12:06
  • Well there was not the simple magic solution I had hoped for, but thanks for explaining why not :) – FelixHJ Jun 15 '16 at 08:33