Safe
Safe catches non-fatal exceptions thrown by the inner stage and converts them into
Status.Complete error values, widening the error type from E to
Either[Throwable, E].
- Exceptions caught during
applyare reported asLeft(throwable)inYield.None(Status.error(Left(e)), ...). - Errors already carried by the inner stage are wrapped as
Right(e).
Fatal exceptions (those not matched by scala.util.control.NonFatal) propagate normally. The evolution is
mapped so that every continuation stage remains wrapped in Safe.
Safe is a crutch for the simplest cases — stateless stages. Per the
Lifecycle contract, a stage that throws from apply
has already released its own resources, so the recovered continuation simply reuses the inner stage — retrying it
on the next input — and carries a no-op dispose. A stage that owns resources should be made safe by itself instead
of being wrapped: extend SafeStage and supply a recover with the appropriate cleanup (the
ConstEvolution overload taking a dispose function exists for exactly this).
import h8io.stages.*
import h8io.stages.base.*
import h8io.stages.operators.*
object ParseIntUnsafe extends SAMStage[String, Int, Nothing] {
override def apply(in: String): Yield[String, Int, Nothing] =
Yield.Some(in.toInt, Status.Success, this)
}
val safe = Safe(ParseIntUnsafe)
// safe: Safe[String, Int, Nothing] = Safe(alterand = <function1>)
safe("42")
// res0: Yield[String, Int, Either[Throwable, Nothing]] = Some(
// out = 42,
// status = Success,
// evolution = Mapped(
// evolution = <function1>,
// f = h8io.stages.operators.Safe$$Lambda$19908/0x00007f58029c4000@1422e059
// )
// )
safe("hello")
// res1: Yield[String, Int, Either[Throwable, Nothing]] = None(
// status = Complete(
// Left(value = java.lang.NumberFormatException: For input string: "hello")
// ),
// evolution = ConstEvolution(
// stage = Safe(alterand = <function1>),
// _dispose = h8io.stages.base.ConstEvolution$$$Lambda$19765/0x00007f5802976ab0@117a8253
// )
// )