Ichor.Toolkit.Result (IchorRuntime v0.1.0)

Copy Markdown View Source

The generic "walk a collection, threading an accumulator through a fallible step, stopping at the first failure" primitive -- extracted from a pattern independently hand-rolled at least ten times across Ichor's own compiler internals before this existed: build_ruleset/1 (duplicated near-verbatim across Ichor.EBNF.ISO.Actions, Ichor.EBNF.W3C.Actions, Ichor.BNF.Actions, Ichor.ABNF.Actions, and Ichor.PEG.Actions), Ichor.Backtrack.Bindings' own unify_compound/5 inner reduce over zipped compound-term arguments (whose failure sentinel is a bare :fail, not {:error, _}), Aether.Eval's own process_defs/2, and -- byte-for-byte identical to each other -- Ichor.Actions' own eval_one/2 list-of-captures branch, and Aether.Eval's own convert_list/3 and convert_seq_terms/3.

Elixir's stdlib has no Enum/Result-traverse equivalent to lean on here (unlike, say, Haskell's Traversable or Rust's Iterator::collect::<Result<_, _>>), which is exactly why this ended up hand-rolled so many times.

Both functions pass any non-{:ok, _} step result straight through, unexamined -- deliberately, so the toolkit never has an opinion on what a "failure" looks like: it works equally for a bare :fail atom (Bindings.unify_compound/5's convention) and for {:error, reason} (every other call site's convention).

Summary

Functions

Maps step over list in order, collecting each value it produces while threading state through sequentially, stopping at the first failure -- exactly the shape eval_one/2's list branch, convert_list/3, and convert_seq_terms/3 each independently wrote (build the result via [v | acc], reverse once at the end, thread state, halt on failure). Built on reduce_ok/2 rather than duplicating its own Enum.reduce_while.

Like Enum.reduce/3, but step returns {:ok, new_acc} to continue or anything else to stop immediately and return that value instead -- the "reduce with early exit on failure" shape build_ruleset/1, unify_compound/5, and process_defs/2 each wrote their own copy of.

Functions

map_ok(list, state, step)

@spec map_ok([elem], state, (elem, state -> {:ok, value, state} | failure)) ::
  {:ok, [value], state} | failure
when elem: term(), state: term(), value: term(), failure: term()

Maps step over list in order, collecting each value it produces while threading state through sequentially, stopping at the first failure -- exactly the shape eval_one/2's list branch, convert_list/3, and convert_seq_terms/3 each independently wrote (build the result via [v | acc], reverse once at the end, thread state, halt on failure). Built on reduce_ok/2 rather than duplicating its own Enum.reduce_while.

reduce_ok(enumerable, acc, step)

@spec reduce_ok(Enumerable.t(elem), acc, (elem, acc -> {:ok, acc} | failure)) ::
  {:ok, acc} | failure
when elem: term(), acc: term(), failure: term()

Like Enum.reduce/3, but step returns {:ok, new_acc} to continue or anything else to stop immediately and return that value instead -- the "reduce with early exit on failure" shape build_ruleset/1, unify_compound/5, and process_defs/2 each wrote their own copy of.