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

izumi.functional.IzEither.scala Maven / Gradle / Ivy

The newest version!
package izumi.functional

import izumi.fundamentals.collections.nonempty.NEList
import izumi.fundamentals.platform.IzPlatformSyntax

import scala.collection.compat.*
import scala.language.implicitConversions

trait IzEither extends IzPlatformSyntax {
  import izumi.functional.IzEither.*

  @inline implicit final def EitherBiAggregate[L, R, Src[_], Col[x] <: IterableOnce[x]](
    col: Col[Either[Src[L], R]]
  ): EitherBiAggregate[L, R, Src, Col] = new EitherBiAggregate(col)

  @inline implicit final def EitherBiFlatAggregate[L, R, Col[x] <: IterableOnce[x], Col2[x] <: IterableOnce[x], Src[_]](
    col: Col[Either[Src[L], Col2[R]]]
  ): EitherBiFlatAggregate[L, R, Col, Col2, Src] = new EitherBiFlatAggregate(col)

  @inline implicit final def EitherScalarOps[L, R, Col[x] <: IterableOnce[x]](col: Col[Either[L, R]]): EitherScalarOps[L, R, Col] = new EitherScalarOps(col)

  @inline implicit final def EitherBiMapAggregate[Col[x] <: IterableOnce[x], T](col: Col[T]): EitherBiMapAggregate[Col, T] = new EitherBiMapAggregate(col)

  @inline implicit final def EitherBiFlatMapAggregate[Col[x] <: IterableOnce[x], T](col: Col[T]): EitherBiFlatMapAggregate[Col, T] = new EitherBiFlatMapAggregate(col)

  @inline implicit final def EitherObjectExt(e: Either.type): EitherExt = new EitherExt(e)

  @inline implicit final def EitherBiTraversals[Col[x] <: IterableOnce[x], T](col: Col[T]): EitherBiTraversals[Col, T] = new EitherBiTraversals(col)

  @inline implicit final def EitherLrPartitions[L, R, Col[x] <: IterableOnce[x]](col: Col[Either[L, R]]): EitherLrPartitions[L, R, Col] = new EitherLrPartitions(col)

  @inline implicit final def EitherTo[L, R, ColR[x] <: IterableOnce[x]](col: Either[L, ColR[R]]): EitherTo[ColR, L, R] = new EitherTo(col)

  @inline protected implicit final def EitherAccumulate[A, ColR[x] <: IterableOnce[x]](col: ColR[A]): EitherAccumulate[A, ColR] = new EitherAccumulate(col)
}

object IzEither extends IzEither {

  final class EitherExt(private val e: Either.type) extends AnyVal {
    def ifThenFail[A](cond: Boolean)(fun: => A): Either[A, Unit] = {
      if (cond) {
        Left(fun)
      } else {
        Right(())
      }
    }

    def ifThenElse[L, R](cond: Boolean)(ok: => R)(fail: => L): Either[L, R] = {
      if (cond) {
        Right(ok)
      } else {
        Left(fail)
      }
    }
  }

  protected final class EitherAccumulate[A, ColR[x] <: IterableOnce[x]](private val col: ColR[A]) extends AnyVal {

    @inline def accumulateErrors[ColL[_], L, R, L1, R1](
      map: A => Either[L, R],
      onLeft: L => IterableOnce[L1],
      onRight: R => Unit,
      end: () => R1,
    )(implicit buildL: Factory[L1, ColL[L1]]
    ): Either[ColL[L1], R1] = {
      val bad = buildL.newBuilder

      val iterator = col.iterator
      var allGood = true
      while (iterator.hasNext) {
        map(iterator.next()) match {
          case Left(e) =>
            allGood = false
            bad ++= onLeft(e)
          case Right(v) =>
            onRight(v)
        }
      }

      if (allGood) {
        Right(end())
      } else {
        Left(bad.result())
      }
    }
  }

  final class EitherBiAggregate[L, R, ColL[_], ColR[x] <: IterableOnce[x]](private val col: ColR[Either[ColL[L], R]]) extends AnyVal {
    @deprecated("use .biSequence")
    def biAggregate(implicit iterL: ColL[L] => IterableOnce[L], buildR: Factory[R, ColR[R]], buildL: Factory[L, ColL[L]]): Either[ColL[L], ColR[R]] = { biSequence }

    /** `sequence` with error accumulation */
    def biSequence(implicit iterL: ColL[L] => IterableOnce[L], buildR: Factory[R, ColR[R]], buildL: Factory[L, ColL[L]]): Either[ColL[L], ColR[R]] = {
      val good = buildR.newBuilder
      col.accumulateErrors(identity, (l: ColL[L]) => iterL(l), (v: R) => good += v, () => good.result())
    }

    @deprecated("use .biSequence_")
    def biAggregateVoid(implicit iterL: ColL[L] => IterableOnce[L], buildL: Factory[L, ColL[L]]): Either[ColL[L], Unit] = {
      biSequence_
    }

    /** `sequence_` with error accumulation */
    def biSequence_(implicit iterL: ColL[L] => IterableOnce[L], buildL: Factory[L, ColL[L]]): Either[ColL[L], Unit] = {
      col.accumulateErrors(identity, (l: ColL[L]) => iterL(l), (_: R) => (), () => ())
    }
  }

  final class EitherScalarOps[L, R, ColR[x] <: IterableOnce[x]](private val col: ColR[Either[L, R]]) extends AnyVal {
    @deprecated("use .biSequenceScalar")
    def biAggregateScalar(implicit buildR: Factory[R, ColR[R]]): Either[NEList[L], ColR[R]] = {
      biSequenceScalar
    }

    /** `sequence` with error accumulation */
    def biSequenceScalar(implicit buildR: Factory[R, ColR[R]]): Either[NEList[L], ColR[R]] = {
      val good = buildR.newBuilder
      col.accumulateErrors(identity, (l: L) => Seq(l), (v: R) => good += v, () => good.result())
    }
  }

  final class EitherTo[ColR[x] <: IterableOnce[x], L, R](private val col: Either[L, ColR[R]]) extends AnyVal {
    def to[CC](buildR: Factory[R, CC]): Either[L, CC] = {
      // just col.map(_.to(buildR)) doesn't work on 2.12
      col.map {
        r =>
          val b = buildR.newBuilder
          b ++= r.iterator
          b.result()
      }
    }
  }

  final class EitherBiMapAggregate[ColR[x] <: IterableOnce[x], T](private val col: ColR[T]) extends AnyVal {
    @deprecated("use .biTraverse")
    def biMapAggregate[ColL[_], L, A](
      f: T => Either[ColL[L], A]
    )(implicit buildR: Factory[A, ColR[A]],
      buildL: Factory[L, ColL[L]],
      iterL: ColL[L] => IterableOnce[L],
    ): Either[ColL[L], ColR[A]] = {
      biTraverse(f)
    }

    /** `traverse` with error accumulation */
    def biTraverse[ColL[_], L, A](
      f: T => Either[ColL[L], A]
    )(implicit buildR: Factory[A, ColR[A]],
      buildL: Factory[L, ColL[L]],
      iterL: ColL[L] => IterableOnce[L],
    ): Either[ColL[L], ColR[A]] = {
      val good = buildR.newBuilder
      col.accumulateErrors(f, (l: ColL[L]) => iterL(l), (v: A) => good += v, () => good.result())
    }

    @deprecated("use .biTraverse(f).to(...)")
    def biMapAggregateTo[ColL[_], L, A, CC](
      f: T => Either[ColL[L], A]
    )(buildRR: Factory[A, CC]
    )(implicit iterL: ColL[L] => IterableOnce[L],
      buildL: Factory[L, ColL[L]],
      buildR: Factory[A, ColR[A]],
    ): Either[ColL[L], CC] = {
      biTraverse(f).to(buildRR)
    }

    @deprecated("use .biTraverse_")
    def biMapAggregateVoid[ColL[_], L](f: T => Either[ColL[L], Unit])(implicit buildL: Factory[L, ColL[L]], iterL: ColL[L] => IterableOnce[L]): Either[ColL[L], Unit] = {
      biTraverse_(f)
    }

    /** `traverse_` with error accumulation */
    def biTraverse_[ColL[_], L](f: T => Either[ColL[L], Unit])(implicit buildL: Factory[L, ColL[L]], iterL: ColL[L] => IterableOnce[L]): Either[ColL[L], Unit] = {
      col.accumulateErrors(f, (l: ColL[L]) => iterL(l), (_: Unit) => (), () => ())
    }
  }

  final class EitherBiFlatMapAggregate[ColR[x] <: IterableOnce[x], T](private val col: ColR[T]) extends AnyVal {
    @deprecated("use .biFlatTraverse")
    def biFlatMapAggregate[ColL[_], L, A](
      f: T => Either[ColL[L], IterableOnce[A]]
    )(implicit buildR: Factory[A, ColR[A]],
      buildL: Factory[L, ColL[L]],
      iterL: ColL[L] => IterableOnce[L],
    ): Either[ColL[L], ColR[A]] = {
      biFlatTraverse(f)
    }

    /** `flatTraverse` with error accumulation */
    def biFlatTraverse[ColL[_], L, A](
      f: T => Either[ColL[L], IterableOnce[A]]
    )(implicit buildR: Factory[A, ColR[A]],
      buildL: Factory[L, ColL[L]],
      iterL: ColL[L] => IterableOnce[L],
    ): Either[ColL[L], ColR[A]] = {
      val good = buildR.newBuilder
      col.accumulateErrors(f, (l: ColL[L]) => iterL(l), (v: IterableOnce[A]) => good ++= v, () => good.result())
    }

    @deprecated("use .biFlatTraverse(f).to(...)")
    def biFlatMapAggregateTo[ColL[_], L, A, CC](
      f: T => Either[ColL[L], IterableOnce[A]]
    )(buildRR: Factory[A, CC]
    )(implicit iterL: ColL[L] => IterableOnce[L],
      buildL: Factory[L, ColL[L]],
      buildR: Factory[A, ColR[A]],
    ): Either[ColL[L], CC] = {
      biFlatTraverse(f).to(buildRR)
    }
  }

  final class EitherBiFlatAggregate[L, R, ColR[x] <: IterableOnce[x], ColIn[x] <: IterableOnce[x], ColL[_]](private val col: ColR[Either[ColL[L], ColIn[R]]])
    extends AnyVal {
    @deprecated("use .biFlatten")
    def biFlatAggregate(implicit buildR: Factory[R, ColR[R]], buildL: Factory[L, ColL[L]], iterL: ColL[L] => IterableOnce[L]): Either[ColL[L], ColR[R]] =
      biFlatten

    /** `flatSequence` with error accumulation */
    def biFlatten(implicit buildR: Factory[R, ColR[R]], buildL: Factory[L, ColL[L]], iterL: ColL[L] => IterableOnce[L]): Either[ColL[L], ColR[R]] = {
      col.biFlatTraverse(identity)
    }
  }

  final class EitherBiTraversals[Col[x] <: IterableOnce[x], T](private val col: Col[T]) extends AnyVal {
    def biFind[E](predicate: T => Either[E, Boolean]): Either[E, Option[T]] = {
      val i = col.iterator

      while (i.hasNext) {
        val a = i.next()
        predicate(a) match {
          case Left(value) =>
            return Left(value)
          case Right(true) =>
            return Right(Some(a))
          case Right(_) =>
        }
      }
      Right(None)
    }

    /** monadic `foldLeft` with short-circuiting error */
    def biFoldLeft[E, A](z: A)(op: (A, T) => Either[E, A]): Either[E, A] = {
      val i = col.iterator
      var acc: Either[E, A] = Right(z)

      while (i.hasNext && acc.isRight) {
        val nxt = i.next()
        (acc, nxt) match {
          case (Right(a), n) =>
            acc = op(a, n)
          case _ =>
        }
      }
      acc
    }

  }

  final class EitherLrPartitions[L, R, Col[x] <: IterableOnce[x]](col: Col[Either[L, R]]) {
    // this doesn't play well with intellisense
    // def partition()(implicit bl: Factory[L, Col[L]], br: Factory[R, Col[R]]): (Col[L], Col[R]) = biPartition

    @deprecated("use .biPartition instead")
    def lrPartition(implicit bl: Factory[L, Col[L]], br: Factory[R, Col[R]]): (Col[L], Col[R]) = biPartition

    def biPartition(implicit bl: Factory[L, Col[L]], br: Factory[R, Col[R]]): (Col[L], Col[R]) = {
      val bad = bl.newBuilder
      val good = br.newBuilder

      col.iterator.foreach {
        case Left(e) => bad += e
        case Right(v) => good += v
      }

      (bad.result(), good.result())
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy