Status

Status is the completion signal that a Stage attaches to every Yield it returns. As stages are composed into a pipeline via ~>, their individual statuses are merged into a single overall status for the run.

Two things can be signalled:

import h8io.stages.*

The Two Variants

Status.Success is the ordinary outcome. The stage did its work and the pipeline is free to move on.

Status.Complete signals that a unit of work has finished. When its errors sequence is empty this represents clean completion; when errors is non-empty one or more errors were accumulated. The signal is passed to Evolution.evolve, which may select a different continuation stage than it would for Success. Enclosing alterators such as Loop and Repeat use it as the cue to stop looping and reset for the next cycle.

Two convenience values cover the common cases:

Combining Statuses

When a pipeline composes two stages with ~> and both run on the same input, their statuses are merged by combine. The rule is simple: the result is whichever of the two is more severe.

Success is the identity element. Combining it with any other status leaves that status unchanged:

Status.Success.combine(Status.Success)
// res0: Status[Nothing] = Success
Status.Success.combine(Status.complete)
// res1: Status[Nothing] = Complete()
Status.Success.combine(Status.error("something went wrong"))
// res2: Status[String] = Complete("something went wrong")

Complete dominates Success. Two Complete values are merged by concatenating their error sequences, preserving the left-to-right order of the stages in the pipeline:

Status.complete.combine(Status.Success)
// res3: Status[Nothing] = Complete()
Status.complete.combine(Status.complete)
// res4: Status[Nothing] = Complete()
Status.error("first").combine(Status.error("second"))
// res5: Status[String] = Complete("first", "second")

Together, combine and Success make Status[E] a monoid for any fixed E: combine is associative, and Success is its identity element. The monoid is not commutative in general — when two Complete values with errors are combined, the order of their error sequences is significant.

Accessing Errors

Status.Complete extends Iterable[E], so errors can be iterated directly with foreach or a for-comprehension. errors holds the full sequence; toList returns them all in order:

val err = Status.error("disk full", "timeout", "connection reset")
// err: Status.Complete[String] = Complete(
//   "disk full",
//   "timeout",
//   "connection reset"
// )
err.errors
// res6: Seq[String] = ArraySeq("disk full", "timeout", "connection reset")
err.toList
// res7: List[String] = List("disk full", "timeout", "connection reset")
for (message <- err) println(message)
// disk full
// timeout
// connection reset

Status.complete has no errors and is therefore empty:

Status.complete.isEmpty
// res9: Boolean = true
Status.complete.toList
// res10: List[Nothing] = List()

Transforming Error Values

map applies a function to every error value contained in a Status.Complete, leaving Status.Success and error-free Complete values unchanged. This is useful when a pipeline boundary needs to change the error representation:

val numeric = Status.error("42", "7", "100").map(_.toInt)
// numeric: Status.Complete[Int] = Complete(42, 7, 100)
numeric.toList
// res11: List[Int] = List(42, 7, 100)

Because Status.Success and Status.complete carry no error values, map returns them unchanged:

(Status.Success: Status[String]).map(identity)
// res12: Status[String] = Success
(Status.complete: Status[String]).map(identity)
// res13: Status[String] = Complete()