All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lucuma.itc.binding.Nullable.scala Maven / Gradle / Ivy

// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause

package lucuma.odb.data

import cats.*

import scala.annotation.tailrec

/** Option-like data type that distinguishes null values from absent ones. */
sealed trait Nullable[+A] extends Product with Serializable {
  import Nullable._

  def fold[B](ifNull: => B, ifAbsent: => B, ifPresent: A => B): B =
    this match {
      case Null       => ifNull
      case Absent     => ifAbsent
      case NonNull(a) => ifPresent(a)
    }

  /** Fold if non-Absent. */
  def foldPresent[B](ifPresent: Option[A] => B): Option[B] =
    fold(Some(ifPresent(None)), None, a => Some(ifPresent(Some(a))))

  def map[B](f:          A => B): Nullable[B]           = fold(Null, Absent, a => NonNull(f(a)))
  def flatMap[B](f:      A => Nullable[B]): Nullable[B] = fold(Null, Absent, f)
  def orElse[B >: A](nb: Nullable[B]): Nullable[B]      = fold(nb, nb, NonNull(_))
  def toOption: Option[A] = fold(None, None, Some(_))

  def toOptionOption: Option[Option[A]] =
    fold(Some(None), None, a => Some(Some(a)))

  def isNull: Boolean =
    fold(ifNull = true, ifAbsent = false, _ => false)

  def isAbsent: Boolean =
    fold(ifNull = false, ifAbsent = true, _ => false)

  def isPresent: Boolean =
    fold(ifNull = false, ifAbsent = false, _ => true)

}

object Nullable {

  case object Null                extends Nullable[Nothing]
  case object Absent              extends Nullable[Nothing]
  case class NonNull[A](value: A) extends Nullable[A]

  implicit val NullableInstances: Monad[Nullable] & SemigroupK[Nullable] =
    new Monad[Nullable] with SemigroupK[Nullable] {
      override def map[A, B](fa: Nullable[A])(fab: A => B): Nullable[B]           = fa.map(fab)
      def flatMap[A, B](fa:      Nullable[A])(f:   A => Nullable[B]): Nullable[B] = fa.flatMap(f)
      def pure[A](x:             A): Nullable[A] = NonNull(x)
      def combineK[A](x:         Nullable[A], y:   Nullable[A]): Nullable[A]      = x.orElse(y)
      @tailrec def tailRecM[A, B](a: A)(f: A => Nullable[Either[A, B]]): Nullable[B] =
        f(a) match {
          case Absent                => Absent
          case Null                  => Null
          case NonNull(Right(value)) => NonNull(value)
          case NonNull(Left(value))  => tailRecM(value)(f)
        }
    }

  def orNull[A](opt: Option[A]): Nullable[A] =
    opt.fold(Null: Nullable[A])(a => NonNull(a))

  def orAbsent[A](opt: Option[A]): Nullable[A] =
    opt.fold(Absent: Nullable[A])(a => NonNull(a))

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy