Or

Or tries left first; if left produces no output, it falls back to right. The result is wrapped in scala.util.Either to distinguish which branch supplied the value. right is never applied when left succeeds.

left right result
Some(l) — not applied — Some(Left(l), leftStatus)
None Some(r) Some(Right(r), combined)
None None None(combined)

Statuses are merged with combine only when both sides are evaluated.

import h8io.stages.*
import h8io.stages.base.*
import h8io.stages.operators.*
object ParseInt extends SAMStage[String, Int, Nothing] {
  override def apply(in: String): Yield[String, Int, Nothing] =
    in.toIntOption match {
      case Some(n) => Yield.Some(n, Status.Success, this)
      case None    => Yield.None(Status.Success, this)
    }
}

object ParseLength extends SAMStage[String, Int, Nothing] {
  override def apply(in: String): Yield[String, Int, Nothing] =
    Yield.Some(in.length, Status.Success, this)
}

val or = Or(ParseInt, ParseLength)
// or: Or[String, Int, Int, Nothing] = Or(
//   left = <function1>,
//   right = <function1>
// )
or("42")     // left succeeds: Left(42)
// res0: Yield[String, Either[Int, Int], Nothing] = Some(
//   out = Left(value = 42),
//   status = Success,
//   evolution = Evolution(left = <function1>, right = <function1>)
// )
or("hello")  // left fails, right succeeds: Right(5)
// res1: Yield[String, Either[Int, Int], Nothing] = Some(
//   out = Right(value = 5),
//   status = Success,
//   evolution = Evolution(left = <function1>, right = <function1>)
// )