samol
samol

Reputation: 20650

How to do null checks in Scala in an idiomatic way?

case class Account(
                          val name: String,
                          val uuid: String,
                          val lat: Double,
                          val lng: Double
                        )
}

object Account {
  def create(row: Row): Option[YellowAccount] = {
    val name = row.getAs[String]("store_name")
    val uuid = row.getAs[String]("uuid")
    val latO = row.getAs[String]("latitude")
    val lngO = row.getAs[String]("longitude")

    // How do you do null checks here in an idiomatic way?
    if (latO == null || lngO == null) {
      return None
    }

    Some(YellowAccount(name, uuid, latO.toDouble, lngO.toDouble))
  }

}

lat/long are compulsory fields in Account. How do you do null checks in an idiomatic way?

Upvotes: 3

Views: 1053

Answers (2)

Dima
Dima

Reputation: 40508

As the other answer suggests, you can use Option to handle possible nulls. You can't use for comprehension the way it is suggested there, but there are several ways around it. The easiest, probably is to .zip the two Options together, and then map over the result:

  Option(row.getAs[latitude])
    .zip(Option(row.getAs[String]("longitude")))
    .map { case (lat, long) => 
      YellowAccount(
        row.getAs[String]("store_name"), 
        row.getAs[String]("uuid"), 
        lat.toDouble, 
        long.toDouble
       )
     }

Upvotes: 0

laughedelic
laughedelic

Reputation: 6460

You can use Option type to handle null values. You just wrap a nullable value in Option and then you can pattern match on it or something else. In your example, I think the most concise way to combine 4 nullable values is for-comprehension:

import scala.util.Try

object Account {
  def create(row: Row): Option[YellowAccount] = {
    for {
      name <- Option( row.getAs[String]("store_name") )
      uuid <- Option( row.getAs[String]("uuid") )
      latO <-    Try( row.getAs[String]("latitude").toDouble ).toOption
      lngO <-    Try( row.getAs[String]("longitude").toDouble ).toOption
    } yield 
      YellowAccount(name, uuid, latO, lngO)  
  }
}

EDIT

Another thing here is _.toDouble conversion, which may throw an exception if it fails to parse the string, so you can wrap it in Try instead (I updated the code above).

EDIT2

To clarify what's happening here:

  • when you wrap a value in Option it becomes None if the value is null, or Some(...) with the value otherwise
  • similarly when wrapping something that may throw an exception in Try, it becomes either Failure with the exception, or Success with the value
  • toOption method converts Try to Option in a straightforward way: Failure becomes None, Success becomes Some
  • in the for-comprehension if any of the four Options returns None (i.e. one of them was null of failed to parse a number), the whole statement returns None, and only if each of the four yields a value, they are passed to the YellowAccount constructor

Upvotes: 5

Related Questions