Search code examples
scalaoperator-overloadingdereference

Improve Groovy Safe Dereference operator in Scala


In response to my question How to develop macro to short-circuit null? someone pointed me to an earlier long thread with many answers, the most compelling of which to was https://stackoverflow.com/a/5569905 . But it doesn't quite work with Scala AnyVal "primitives" like Int:

object TestOp {

  class SafeDereference[A](obj: A) {
    def ?[B >: Null](function: A => B): B = if (obj == null) null else function(obj)
  }
  implicit def safeDereference[A](obj: A) = new SafeDereference(obj)

  class C { 
    def getValue: Int = 0                     
  }

  def main(args: Array[String]) {
    val c = new C
    val x:Int = c ? (_.getValue)
  }
}

gives a compilation error of:

[error] TestOp.scala:14: type mismatch;
[error]  found   : Any
[error]  required: Int
[error]     val x:Int = c ? (_.getValue)
[error]                   ^
[error] one error found
[error] {file:/home/mmalak/streaming-demo/}default-ae36bd/compile:compile: Compilation failed

A workaround is to replace val x:Int with val x:java.lang.Integer, and that will compile. Is there a way to improve SafeDereference above so that val x:Int is allowed?

Additional information

The following produces the desired output. The question now becomes how to move the typecasts into SafeDereference, and how to handle all the other Scala "primitives" (Boolean etc).

object TestOp {

  class SafeDereference[A](obj: A) {
    def ?[B >: Null](function: A => B): B = if (obj == null) null else function(obj)
  }
  implicit def safeDereference[A](obj: A) = new SafeDereference(obj)

  class C { 
    def getValue: Int = 0                     
  }

  def main(args: Array[String]) {
    val c:C = null
    val x = (c ? (_.getValue)).asInstanceOf[java.lang.Integer].asInstanceOf[Int]
    println("x="+x)
  }
}

outputs, as desired:

x=0

Solution

  • You could do something like this. The Zero trait allows you to determine the zero value for any object that is not nullable. In this case I added one for Numeric types:

    object TestOp {
    
      trait Zero[T] {
        def apply(): T
      }
    
      object Zero {
        implicit def zeroNull[B >: Null] =
          new Zero[B] { def apply = null }
    
        implicit def zeroNumeric[B: Numeric] =
          new Zero[B] { def apply = implicitly[Numeric[B]].zero }
      }
    
      implicit class SafeDereference[A](obj: A) {
        def ?[B](function: A => B)(implicit zero: Zero[B]): B =
          if (obj == null) zero() else function(obj)
      }
    
      class C {
        def getValue: Int = 0
        def getSomething: C = new C
      }
    
      def main(args: Array[String]) {
        val c = new C
        val x = c ? (_.getValue)
        val y = c ? (_.getSomething)
      }
    }
    

    Edit

    For Boolean you would add something like this:

    implicit def zeroBoolean[B >: Boolean] =
      new Zero[B] { def apply = false }