jxcoder jxcoder - 3 days ago 6
Scala Question

How to avoid using null?

Suppose, I have my domain object named "Office":

case class Office(
id: Long,
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
null.asInstanceOf[Long], name, phone, address
)
}


When I create new Office:

new Office("officeName","00000000000", "officeAddress")


I don't specify id field becouse I don't know it. When I save office (by Anorm) I now id and do that:

office.id = officeId


So. I know that using null is non-Scala way. How to avoid using null in my case?

UPDATE #1

Using Option.

Suppose, something like this:

case class Office(
id: Option[Long],
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
None, name, phone, address
)
}


And, after saving:

office.id = Option(officeId)


But what if I need to find something by office id?

SomeService.findSomethingByOfficeId(office.id.get)


Does it clear? office.id.get looks not so good)

UPDATE #2

Everyone thanks! I've got new ideas from your answers! Greate thanks!

Answer

Just make the id field an Option[Long]; once you have that, you can use it like this:

office.id.map(SomeService.findSomethingByOfficeId)

This will do what you want and return Option[Something]. If office.id is None, map() won't even invoke the finder method and will immediately return None, which is what you want typically.

And if findSomethingByOfficeId returns Option[Something] (which it should) instead of just Something or null/exception, use:

office.id.flatMap(SomeService.findSomethingByOfficeId)

This way, if office.id is None, it will, again, immediately return None; however, if it's Some(123), it will pass that 123 into findSomethingByOfficeId; now if the finder returns a Some(something) it will return Some(Something), if however the finder returns None, it will again return None.

if findSomethingByOfficeId can return null and you can't change its source code, wrap any calls to it with Option(...)—that will convert nulls to None and wrap any other values in Some(...); if it can throw an exception when it can't find the something, wrap calls to it with Try(...).toOption to get the same effect (although this will also convert any unrelated exceptions to None, which is probably undesirable, but which you can fix with recoverWith).

The general guideline is always avoid null and exceptions in Scala code (as you stated); always prefer Option[T] with either map or flatMap chaining, or using the monadic for syntactic sugar hiding the use of map and flatMap.

Runnable example:

object OptionDemo extends App {
  case class Something(name: String)
  case class Office(id: Option[Long])

  def findSomethingByOfficeId(officeId: Long) = {
    if (officeId == 123)
      Some(Something("London"))
    else
      None
  }

  val office1 = Office(id = None)
  val office2 = Office(id = Some(456))
  val office3 = Office(id = Some(123))

  println(office1.id.flatMap(findSomethingByOfficeId))
  println(office2.id.flatMap(findSomethingByOfficeId))
  println(office3.id.flatMap(findSomethingByOfficeId))
}

Output:

None
None
Some(Something(London))

For a great introduction to Scala's rather useful Option[T] type, see http://danielwestheide.com/blog/2012/12/19/the-neophytes-guide-to-scala-part-5-the-option-type.html.

Comments