Skip to content

A Nullable type for scala3 #4822

@johnynek

Description

@johnynek

I was using explicit nulls recently in a scala3 only project and had to deal with a java API that returned null. I took a moment to make a small wrapper to use a more FP style:

import scala.quoted.*

opaque type Nullable[+T] = T | Null

object Nullable:

  extension [T](inline nullable: Nullable[T])
    transparent inline def fold[A](inline ifNull: => A)(inline fn: T => A): A =
      ${ foldImpl('nullable, 'ifNull, 'fn) }

    inline def isNull: Boolean =
      fold(true)(_ => false)

    inline def nonNull: Boolean =
      fold(false)(_ => true)

    inline def map[B](inline fn: T => B): Nullable[B] =
      fold(null: Null)(fn)

    inline def flatMap[B](inline fn: T => Nullable[B]): Nullable[B] =
      fold(null: Null)(fn)

    inline def toOption: Option[T] =
      fold(None)(Some(_))

    inline def iterator: Iterator[T] =
      fold(Iterator.empty)(Iterator.single(_))

  inline def apply[A](inline a: A | Null): Nullable[A] =
    a

  private def foldImpl[T: Type, A: Type](
      nullable: Expr[Nullable[T]],
      ifNull: Expr[A],
      fn: Expr[T => A]
  )(using Quotes): Expr[A] =
    '{
      val n = $nullable
      given CanEqual[T | Null, Null] = CanEqual.derived
      if (n == null) $ifNull
      else {
        val safe: T = n.asInstanceOf[T]
        ${ Expr.betaReduce('{ $fn(safe) }) }
      }
    }

I think this type could have lawful implementations of many typeclasses: Monad, Traverse, Foldable, Hash, Order, Eq, Monoid, Group (maybe more algebraic typeclasses).

Should I flesh this out and make a PR to add it to cats.data perhaps? It could be nice to have a zero-cost abstraction for nullable types from Java and javascript interop.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions