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

japgolly.microlibs.utils.PotentialChange.scala Maven / Gradle / Ivy

The newest version!
package japgolly.microlibs.utils

import cats.Eq
import japgolly.microlibs.nonempty.NonEmpty
import japgolly.microlibs.utils.PotentialChange._
import japgolly.univeq._
import scala.annotation.nowarn

sealed abstract class PotentialChange[+E, +A] {
  final def foreach(f: A => Unit): Unit =
    this match {
      case Success(a)             => f(a)
      case Failure(_) | Unchanged => ()
    }

  final def map[B](f: A => B): PotentialChange[E, B] =
    flatMap(a => Success(f(a)))

  final def flatMap[B, EE >: E](f: A => PotentialChange[EE, B]): PotentialChange[EE, B] =
    this match {
      case Success(a)   => f(a)
      case x@Failure(_) => x
      case Unchanged    => Unchanged
    }

  final def mapFailure[F](f: E => F): PotentialChange[F, A] =
    flatMapFailure(e => Failure(f(e)))

  final def flatMapFailure[F, AA >: A](f: E => PotentialChange[F, AA]): PotentialChange[F, AA] =
    this match {
      case x@Success(_) => x
      case Failure(e)   => f(e)
      case Unchanged    => Unchanged
    }

  final def ap[EE >: E, AA >: A, B, C](fb: PotentialChange[EE, B])
                                      (f: (Option[AA], Option[B]) => Option[C]): PotentialChange[EE, C] =
    (this, fb) match {
      case (x: NonFailure[AA], y: NonFailure[B]) => PotentialChange.fromOption(f(x.getUpdate, y.getUpdate))
      case (f: Failure[EE], _)                   => f
      case (_, f: Failure[EE])                   => f
    }

  final def merge[EE >: E, AA >: A, B, C](fb: PotentialChange[EE, B])
                                         (originalA: => AA, originalB: => B)
                                         (f: (AA, B) => C): PotentialChange[EE, C] =
    ap(fb)((oa, ob) => Some(f(oa getOrElse originalA, ob getOrElse originalB)))

  final def getUpdate: Option[A] =
    this match {
      case Success(a)             => Some(a)
      case Failure(_) | Unchanged => None
    }

  final def getFailure: Option[E] =
    this match {
      case Success(_) | Unchanged => None
      case Failure(e)             => Some(e)
    }

  final def isFailure: Boolean =
    this match {
      case Success(_) | Unchanged => false
      case Failure(_)             => true
    }

  final def isSuccess: Boolean =
    this match {
      case Success(_)             => true
      case Failure(_) | Unchanged => false
    }

  final def isUnchanged: Boolean =
    this match {
      case Success(_) | Failure(_) => false
      case Unchanged               => true
    }

  @inline final def isChanged: Boolean =
    !isUnchanged

  /** [[Unchanged]] is considered valid. */
  final def validity: Validity =
    Invalid when isFailure

  final def filter(f: A => Boolean): PotentialChange[E, A] =
    ignore(!f(_))

  final def ignore(f: A => Boolean): PotentialChange[E, A] =
    this match {
      case Success(a) if !f(a) => Unchanged
      case _                   => this
    }

  final def ignoreValue[AA >: A](a: => AA)(implicit e: Eq[AA]): PotentialChange[E, A] =
    ignore(e.eqv(a, _))

  final def ignoreOption[AA >: A](o: => Option[AA])(implicit e: Eq[AA]): PotentialChange[E, A] =
    ignore(a => o.fold(false)(e.eqv(a, _)))

  final def ignoreEmpty[AA >: A, B](implicit p: NonEmpty.Proof[AA, B]): PotentialChange[E, B] =
    flatMap(PotentialChange.nonEmpty[AA, B](_)(p))

  final def setDiff[B](prev: Set[B])(implicit ev: A <:< Set[B], univEq: UnivEq[B]): PotentialChange[E, SetDiff.NE[B]] =
    flatMap(a => PotentialChange.fromOption(NonEmpty(SetDiff.compare(prev, ev(a)))))

  final def setDiffOption[B](prev: Option[Set[B]])(implicit ev: A <:< Set[B], univEq: UnivEq[B]): PotentialChange[E, SetDiff.NE[B]] =
    flatMap(a => PotentialChange.fromOption(NonEmpty(SetDiff.compareOption(prev, ev(a)))))

  final def toOption: Option[A] =
    this match {
      case Success(a)             => Some(a)
      case Failure(_) | Unchanged => None
    }

  final def toEitherOption: Either[E, Option[A]] =
    this match {
      case Success(a) => Right(Some(a))
      case Unchanged  => Right(None)
      case Failure(e) => Left(e)
    }
}

object PotentialChange {

  sealed trait NonFailure[+A] extends PotentialChange[Nothing, A] {
    final def foldNonFailure[B](changed: A => B, unchanged: => B): B =
      this match {
        case Success(a) => changed(a)
        case Unchanged  => unchanged
      }
  }

  sealed trait Changed[+E, +A] extends PotentialChange[E, A]

  case object Unchanged extends NonFailure[Nothing]

  final case class Success[+A](update: A) extends NonFailure[A] with Changed[Nothing, A]

  final case class Failure[+E](failure: E) extends Changed[E, Nothing]

  @nowarn("cat=unused")
  implicit def univEq[E: UnivEq, A: UnivEq]: UnivEq[PotentialChange[E, A]] =
    UnivEq.derive

  // no compare, compareOption etc because they're methods on instances instead

  def fromEither[E, A](d: Either[E, A]): PotentialChange[E, A] =
    d match {
      case Right(a) => Success(a)
      case Left(e)  => Failure(e)
    }

  def fromOption[A](o: Option[A]): NonFailure[A] =
    o match {
      case Some(a) => Success(a)
      case None    => Unchanged
    }

  def needFromOption[A](o: Option[A]): PotentialChange[Unit, A] =
    o match {
      case Some(a) => Success(a)
      case None    => Failure(())
    }

  def nonEmpty[A, B](a: A)(implicit p: NonEmpty.Proof[A, B]): NonFailure[B] =
    fromOption(p tryProve a)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy