1

I have two questions about nullable types in Scala:

  1. Let's say I wish to define a new class: class myClass[T](x: T), and I'd like to make sure that T is nullable. How do I do that?

  2. I'd like to write a function def myFunc(x: T) (not as part of the previous question), and I'd like to perform one thing if T is nullable or another if not. The difference from the previous question is that here I don't wish to limit T, but rather know if it's nullable or not. How do I do that?

shakedzy
  • 2,853
  • 5
  • 32
  • 62

4 Answers4

5

In scala, all types that extend AnyRef (equivalent of Object) are nullable. Most of the scala community avoids using nulls though, and tends to be more explicit by representing the existence/absence of a value with Option.

Alvaro Carrasco
  • 6,103
  • 16
  • 24
4

1. Use a >: Null <: AnyRef bound:

@ def f[T >: Null <: AnyRef](arg: T): T = arg
defined function f

@ f(10)
cmd3.sc:1: inferred type arguments [Any] do not conform to method f's type parameter bounds [T >: Null <: AnyRef]
val res3 = f(10)
           ^
cmd3.sc:1: type mismatch;
 found   : Int(10)
 required: T
val res3 = f(10)
             ^
Compilation Failed

@ f("a")
res3: String = "a"

2. Use implicit type constraints with default values:

@ def isNullable[T](arg: T)(implicit sn: Null <:< T = null, sar: T <:< AnyRef = null): Boolean = sn != null && sar != null
defined function isNullable

@ isNullable(10)
res8: Boolean = false

@ isNullable("a")
res9: Boolean = true

These are similar to static type bounds, except that they are performed during implicit resolution instead of type checking and therefore permit failures if you provide default values for them (nulls in this case, no pun intended :))

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
4

"Nullable" means that it is a subclass of AnyRef (and not Nothing), therefore, you can enforce that MyClass takes only nullable instances as follows:

case class MyClass[T <: AnyRef](t: T)

MyClass("hey")
MyClass[String](null)
MyClass(null)
// MyClass[Int](3) won't compile, because `Int` is primitive

To determine whether a type is nullable, you could provide implicit methods that generate nullability tokens:

sealed trait Nullability[-T]
case object Nullable extends Nullability[AnyRef] {
  def isNull(t: Any): Boolean = t == null
}
case object NotNullable extends Nullability[AnyVal]
object Nullability {
  implicit def anyRefIsNullable[T <: AnyRef]: Nullability[T] = Nullable
  implicit def anyValIsNotNullable[T <: AnyVal]: Nullability[T] = NotNullable
}

def myFunc[T](t: T)(implicit nullability: Nullability[T]): Unit = {
  nullability match {
    case Nullable => 
      if (t == null) {
        println("that's a null")
      } else {
        println("that's a non-null object: " + t)
      }
    case NotNullable => println("That's an AnyVal: " + t)
  }
}

Now you can use myFunc as follows:

myFunc("hello")
myFunc(null)
myFunc(42)

// outputs:
// that's a non-null object: hello
// that's a null
// That's an AnyVal: 42

This won't compile if you try to use myFunc on Any, because the compiler won't be able to determine whether it's AnyRef or AnyVal, and the two implicit methods will clash. In this way, it can be ensured at compile time that we don't accidentally use myFunc on Any, for which the nullability cannot be determined at compile time.

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
2

Even though we don't use null in Scala that often (in favor of Option) you may force function to take nullable parameter with

def f[T <: AnyRef](x: T) = ???
bottaio
  • 4,963
  • 3
  • 19
  • 43
  • 1
    Note that this is not enough in general - `T` must also be a supertype of `Null` to be nullable (since `Nothing` is not nullable). Although in the case when you have values of the passed type, it probably does not matter. – Vladimir Matveev Feb 09 '18 at 21:50
  • @VladimirMatveev Good catch! It actually could matter if the signature were something like `def f[T <: AnyRef](x: => T) = ???`, because if one could differentiate between something and `Nothing`, one could skip the evaluation of `x` in the `Nothing`-case, without throwing any runtime exceptions. – Andrey Tyukin Feb 09 '18 at 22:16