IOr

IOr is a binary operator that applies both left and right to the same input independently and combines their outputs into a cats.data.Ior. Unlike And (which skips right when left yields nothing) and Or (which skips right when left succeeds), IOr always runs both stages and handles all four combinations:

left \ right Some None
Some Ior.Both(l, r) Ior.Left(l)
None Ior.Right(r) Yield.None

Statuses from both sides are always merged with combine.

import h8io.stages.*
import h8io.stages.base.*
import h8io.stages.cats.*
object IsPositive extends SAMStage[Int, Int, Nothing] {
  override def apply(in: Int): Yield[Int, Int, Nothing] =
    if (in > 0) Yield.Some(in, Status.Success, this) else Yield.None(Status.Success, this)
}

object IsEven extends SAMStage[Int, Int, Nothing] {
  override def apply(in: Int): Yield[Int, Int, Nothing] =
    if (in % 2 == 0) Yield.Some(in, Status.Success, this)
    else Yield.None(Status.Success, this)
}

val ior = IOr(IsPositive, IsEven)
// ior: IOr[Int, Int, Int, Nothing] = IOr(
//   left = <function1>,
//   right = <function1>
// )
ior(4)   // both succeed: Ior.Both
// res0: Yield[Int, <none>.<root>.cats.data.Ior[Int, Int], Nothing] = Some(
//   out = Both(a = 4, b = 4),
//   status = Success,
//   evolution = Evolution(left = <function1>, right = <function1>)
// )
ior(3)   // left only:    Ior.Left
// res1: Yield[Int, <none>.<root>.cats.data.Ior[Int, Int], Nothing] = Some(
//   out = Left(a = 3),
//   status = Success,
//   evolution = Evolution(left = <function1>, right = <function1>)
// )
ior(-2)  // right only:   Ior.Right
// res2: Yield[Int, <none>.<root>.cats.data.Ior[Int, Int], Nothing] = Some(
//   out = Right(b = -2),
//   status = Success,
//   evolution = Evolution(left = <function1>, right = <function1>)
// )
ior(-3)  // neither:      Yield.None
// res3: Yield[Int, <none>.<root>.cats.data.Ior[Int, Int], Nothing] = None(
//   status = Success,
//   evolution = Evolution(left = <function1>, right = <function1>)
// )

Projections

IOr.Left and IOr.Right are projections for cats.data.Ior. Each extracts one side when present, yielding nothing otherwise. Ior.Both satisfies both projections simultaneously.

import _root_.cats.data.Ior
val leftProj  = IOr.Left[Int]
// leftProj: Projection[Ior[Int, ?$2], Int] = <function1>
val rightProj = IOr.Right[Int]
// rightProj: Projection[Ior[?$5, Int], Int] = <function1>

leftProj(Ior.Left(1))
// res4: Yield[Ior[Int, ?$2], Int, Nothing] = Some(
//   out = 1,
//   status = Success,
//   evolution = <function1>
// )
leftProj(Ior.Right(2))
// res5: Yield[Ior[Int, ?$2], Int, Nothing] = None(
//   status = Success,
//   evolution = <function1>
// )
leftProj(Ior.Both(1, 2))
// res6: Yield[Ior[Int, ?$2], Int, Nothing] = Some(
//   out = 1,
//   status = Success,
//   evolution = <function1>
// )

rightProj(Ior.Left(1))
// res7: Yield[Ior[?$5, Int], Int, Nothing] = None(
//   status = Success,
//   evolution = <function1>
// )
rightProj(Ior.Right(2))
// res8: Yield[Ior[?$5, Int], Int, Nothing] = Some(
//   out = 2,
//   status = Success,
//   evolution = <function1>
// )
rightProj(Ior.Both(1, 2))
// res9: Yield[Ior[?$5, Int], Int, Nothing] = Some(
//   out = 2,
//   status = Success,
//   evolution = <function1>
// )