25

Having seen the answers coming out of questions like this one involving horror shows like trying to catch the NPE and dredge the mangled name out of the stack trace, I am asking this question so I can answer it.

Comments or further improvements welcome.

Community
  • 1
  • 1
psp
  • 12,138
  • 1
  • 41
  • 51
  • 3
    You should have answered that question instead of creating a new one. Or, perhaps, in addition to. – Daniel C. Sobral Sep 01 '09 at 23:59
  • 4
    If you say so. When I answer a question more than about 24 hours after it's asked the answer languishes in obscurity, regardless of its relative merit (and given that I only answer scala questions I have some idea of the value of my answer.) – psp Sep 03 '09 at 02:28
  • thanks, it was a horror to see a solution that involves exceptions. unless you want to slow down your application several order of magnitude. – smartnut007 Aug 07 '12 at 03:48
  • 2
    [Best Scala imitation of Groovy's safe-dereference operator (?.)?](http://stackoverflow.com/questions/1163393/best-scala-imitation-of-groovys-safe-dereference-operator) – Mechanical snail Aug 07 '12 at 11:22

2 Answers2

39

Like so:

case class ?:[T](x: T) {
  def apply(): T = x
  def apply[U >: Null](f: T => U): ?:[U] =
    if (x == null) ?:[U](null)
    else ?:[U](f(x))
}

And in action:

scala> val x = ?:("hel")(_ + "lo ")(_ * 2)(_ + "world")()
x: java.lang.String = hello hello world

scala> val x = ?:("hel")(_ + "lo ")(_ => (null: String))(_ + "world")()
x: java.lang.String = null
psp
  • 12,138
  • 1
  • 41
  • 51
0

Added orElse

case class ?:[T](x: T) {
  def apply(): T = x
  def apply[U >: Null](f: T => U): ?:[U] =
    if (x == null) ?:[U](null)
    else ?:[U](f(x))
  def orElse(y: T): T = 
    if (x == null) y
    else x
}
scala> val x = ?:(obj)(_.subField)(_.subSubField).orElse("not found")
x: java.lang.String = not found

Or if you prefer named syntax as opposed to operator syntax

case class CoalesceNull[T](x: T) {
  def apply(): T = x
  def apply[U >: Null](f: T => U): CoalesceNull[U] =
    if (x == null) CoalesceNull[U](null)
    else CoalesceNull[U](f(x))
  def orElse(y: T): T =
    if (x == null) y
    else x
}
scala> val x = CoalesceNull(obj)(_.subField)(_.subSubField).orElse("not found")
x: java.lang.String = not found

More examples

case class Obj[T](field: T)

  test("last null") {
    val obj: Obj[Obj[Obj[Obj[String]]]] = Obj(Obj(Obj(Obj(null))))
    val res0 = CoalesceNull(obj)(_.field)(_.field)(_.field)(_.field)()
    res0 should === (null)
    val res1 = CoalesceNull(obj)(_.field)(_.field)(_.field)(_.field).orElse("not found")
    res1 should === ("not found")
  }

  test("first null") {
    val obj: Obj[Obj[Obj[Obj[String]]]] = null
    val res0 = CoalesceNull(obj)(_.field)(_.field)(_.field)(_.field)()
    res0 should === (null)
    val res1 = CoalesceNull(obj)(_.field)(_.field)(_.field)(_.field).orElse("not found")
    res1 should === ("not found")
  }

  test("middle null") {
    val obj: Obj[Obj[Obj[Obj[String]]]] = Obj(Obj(null))
    val res0 = CoalesceNull(obj)(_.field)(_.field)(_.field)(_.field)()
    res0 should === (null)
    val res1 = CoalesceNull(obj)(_.field)(_.field)(_.field)(_.field).orElse("not found")
    res1 should === ("not found")
  }

  test("not null") {
    val obj: Obj[Obj[Obj[Obj[String]]]] = Obj(Obj(Obj(Obj("something"))))
    val res0 = CoalesceNull(obj)(_.field)(_.field)(_.field)(_.field)()
    res0 should === ("something")
    val res1 = CoalesceNull(obj)(_.field)(_.field)(_.field)(_.field).orElse("not found")
    res1 should === ("something")
  }
}
Gabriel
  • 1,679
  • 3
  • 16
  • 37