GreyCat
GreyCat

Reputation: 17104

Scala best practices: simple Option[] usage

As a Java-to-Scala switcher, I routinely find myself rewriting null handling stuff like

val itemOpt: Option[Item] = items.get(coords) // "items" is something like a Map
if (itemOpt.isDefined) {
  val item = itemOpt.get
  // do something with item, querying item fields a lot of times, for example
  if (item.qty > 10) {
    storeInVault(item.name, item.qty, coords)
  } else {
    storeInRoom(item)
  }
}

I guess it looks ugly and it really looks like a piece of code rewritten from Java's:

Item item = items.get(coords);
if (item != null) {
  // do something with item, querying item fields a lot of times, for example
}

It also looks ugly in Java, but at least it's one line less. What is the best practice to handle such simple cases in Scala? I already know of flatMap and flatten to handle collections of Option[Stuff], and I know of getOrElse to handle default values. I dream of something like:

items.get(coords).doIfDefined(item =>
  // do stuff with item
)

but I don't see anything like that in Option API.

Upvotes: 9

Views: 1528

Answers (4)

tenshi
tenshi

Reputation: 26566

Very popular usage pattern:

val item: Option[Int] = None
val result = item map (_ + 1) getOrElse 0

so you just use map in order to transform value if it's defined.

If you just want to use value, that is stored within an Option, then just use foreach:

item foreach { it =>
    println(it)
}

As you can see, Option also supports many collection methods, so you actually don't need to learn new API. You can just treat it as a collection with either 1 or 0 elements.

Upvotes: 12

Brian Hsu
Brian Hsu

Reputation: 8821

Also, Option could be used in Pattern Matching with if guard. personally I like use it in this scenario, and think it is easier to read.

Because map on a Option will only has effect when you Option is not None, so you could do it first, and then using Pattern Matching to inspect to see if your itemOpt is a Some or None.

def shouldInVault(item: Item) = item.qty > 10

val itemOpt: Option[Item] = items.get(coords).map(...)

itemOpt match {
    case Some(item) if shouldInVault(item) => storeInVault(item.name, item.qty, coords)
    case Some(item) => storeInRoom(item)
    case None =>
}

Upvotes: 3

Sergey Passichenko
Sergey Passichenko

Reputation: 6920

Check this Tony Morris post. It helped me very much when I tried to understand Option. Your code may be rewritten in such way:

for (item <- items.get(coords)) { // "items" is something like a Map
  // do something with item, querying item fields a lot of times, for example
  if (item.qty > 10) {
    storeInVault(item.name, item.qty, coords)
  } else {
    storeInRoom(item)
  }
}

Upvotes: 5

jcern
jcern

Reputation: 7848

This should accomplish what you are looking to do:

items.get(coords).foreach{ item => 
  //do stuff
}

Upvotes: 7

Related Questions