IAnd

IAnd (Independent And) applies both left and right to the same input unconditionally and produces a (LO, RO) tuple when both succeed. Unlike And, neither side is short-circuited: both stages always run.

left \ right Some None
Some Some((l, r), combined) None(combined)
None None(combined) None(combined)

Statuses from both sides are always merged with combine.

import h8io.stages.*
import h8io.stages.base.*
import h8io.stages.operators.*
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 iand = IAnd(IsPositive, IsEven)
// iand: IAnd[Int, Int, Int, Nothing] = IAnd(
//   left = <function1>,
//   right = <function1>
// )
iand(4)   // both succeed
// res0: Yield[Int, (Int, Int), Nothing] = Some(
//   out = (4, 4),
//   status = Success,
//   evolution = Evolution(left = <function1>, right = <function1>)
// )
iand(3)   // IsPositive succeeds, IsEven fails: both still run
// res1: Yield[Int, (Int, Int), Nothing] = None(
//   status = Success,
//   evolution = Evolution(left = <function1>, right = <function1>)
// )
iand(-2)  // IsPositive fails, IsEven succeeds: both still run
// res2: Yield[Int, (Int, Int), Nothing] = None(
//   status = Success,
//   evolution = Evolution(left = <function1>, right = <function1>)
// )