Searching and testing
Asking a question of a sequence rather than transforming it. The short-circuiting ones — First, Find, Any, All, None — stop as soon as the answer is settled, so they terminate on an infinite source; Last, Count and FindLast cannot, because the answer depends on the final element.
func (s Seq[T]) First() (T, bool)First returns the first element.
// The bool tells an empty sequence apart from a first element that// happens to be the zero value.queue := catena.Of("deploy api", "rotate keys")job, ok := queue.First()_, idle := catena.Empty[string]().First()fmt.Println(job, ok, idle)deploy api true falseFirstOr
Section titled “FirstOr”func (s Seq[T]) FirstOr(fallback T) TFirstOr returns the first element, or fallback if the sequence is empty — First for use inside an expression.
// First, for use inside an expression: no second result to check.ports := catena.Of(8080, 8081)var none catena.Seq[int]fmt.Println(ports.FirstOr(80), none.FirstOr(80))8080 80func (s Seq[T]) Last() (T, bool)Last returns the final element.
// The most recent of a series of health checks.checks := catena.Of(200, 200, 503)latest, ok := checks.Last()fmt.Println(latest, ok)503 trueLastOr
Section titled “LastOr”func (s Seq[T]) LastOr(fallback T) TLastOr returns the final element, or fallback if the sequence is empty.
versions := catena.Of("v1", "v2", "v3")fmt.Println(versions.LastOr("v0"), catena.Empty[string]().LastOr("v0"))v3 v0Single
Section titled “Single”func (s Seq[T]) Single() (T, bool)Single returns the element iff the sequence has exactly one; it stops consuming upon seeing a second.
// True only for exactly one element — the check for "there must be// one primary". It stops as soon as a second arrives rather than// counting the rest.type node struct{ Name, Role string }isPrimary := func(n node) bool { return n.Role == "primary" }
healthy := catena.Of(node{"db-1", "primary"}, node{"db-2", "replica"})splitBrain := catena.Of(node{"db-1", "primary"}, node{"db-2", "primary"})
primary, ok := healthy.Filter(isPrimary).Single()_, unambiguous := splitBrain.Filter(isPrimary).Single()fmt.Println(primary.Name, ok, unambiguous)db-1 true falseElementAt
Section titled “ElementAt”func (s Seq[T]) ElementAt(i int) (T, bool)ElementAt returns the element at index i; (zero, false) for a negative or out-of-range index.
// Zero-based. An index out of range, or negative, reports false// rather than panicking — there is no counting from the end.leaderboard := catena.Of("mira", "tom", "ada")runnerUp, ok := leaderboard.ElementAt(1)_, fromEnd := leaderboard.ElementAt(-1)fmt.Println(runnerUp, ok, fromEnd)tom true falsefunc (s Seq[T]) Find(pred func(T) bool) (T, bool)Find returns the first element pred admits.
// The first request that broke the 500ms budget; nothing after it is// examined.type request struct { Path string Ms int}requests := catena.Of(request{"/home", 80}, request{"/search", 740}, request{"/export", 2100})slow, ok := requests.Find(func(r request) bool { return r.Ms > 500 })fmt.Println(slow.Path, ok)/search trueFindLast
Section titled “FindLast”func (s Seq[T]) FindLast(pred func(T) bool) (T, bool)FindLast returns the final element pred admits.
// The most recent release that passed — the one to roll back to.type deploy struct { Version string Healthy bool}history := catena.Of(deploy{"v41", true}, deploy{"v42", true}, deploy{"v43", false})good, ok := history.FindLast(func(d deploy) bool { return d.Healthy })fmt.Println(good.Version, ok)v42 trueFindIndex
Section titled “FindIndex”func (s Seq[T]) FindIndex(pred func(T) bool) intFindIndex returns the index of the first element pred admits; -1 if none.
// Where the first match is, counted from zero; -1 when there is none.log := catena.Of("INFO boot", "INFO listening on :8080", "ERROR disk full")hasLevel := func(level string) func(string) bool { return func(line string) bool { return strings.HasPrefix(line, level) }}fmt.Println(log.FindIndex(hasLevel("ERROR")))fmt.Println(log.FindIndex(hasLevel("FATAL")))2-1FindMap
Section titled “FindMap”func (s Seq[T]) FindMap[U any](f func(T) (U, bool)) (U, bool)FindMap returns the first mapped value f reports true for — a fused Find + Map.
// Fused find and map: the parsed value is returned, not the header// line it came from, and the lines after it are never parsed.headers := catena.Of("Host: example.com", "Content-Length: 348", "Accept: */*")length, ok := headers.FindMap(func(h string) (int, bool) { value, found := strings.CutPrefix(h, "Content-Length: ") if !found { return 0, false } n, err := strconv.Atoi(value) return n, err == nil})fmt.Println(length, ok)348 truefunc (s Seq[T]) Any(pred func(T) bool) boolAny reports whether pred admits any element; stops at the first match.
// Stops at the first match, so it terminates even on an endless// source. Here the two lines after the error are never read.read := 0log := catena.Of("INFO boot", "ERROR disk full", "INFO retrying", "INFO ok"). OnEach(func(string) { read++ })
failed := log.Any(func(line string) bool { return strings.HasPrefix(line, "ERROR") })fmt.Println(failed, read)true 2func (s Seq[T]) All(pred func(T) bool) boolAll reports whether pred admits every element; stops at the first counterexample. Vacuously true on empty input.
// Vacuously true on an empty sequence: a pipeline with no checks// "passes", so guard for that separately if it matters.type check struct { Name string Passed bool}passed := func(c check) bool { return c.Passed }
ci := catena.Of(check{"lint", true}, check{"unit", true}, check{"e2e", true})fmt.Println(ci.All(passed))fmt.Println(catena.Empty[check]().All(passed))truetruefunc (s Seq[T]) None(pred func(T) bool) boolNone reports whether pred admits no element; stops at the first match.
// "Did anything go wrong?" asked the other way round; like Any, it// stops at the first match.statuses := catena.Of(200, 204, 301, 404)fmt.Println(statuses.None(func(code int) bool { return code >= 500 }))truefunc (s Seq[T]) Count() intCount returns the number of elements.
// Counts by consuming: nothing is collected along the way.body := "the quick brown fox jumps over the lazy dog"fmt.Println(catena.FromSlice(strings.Fields(body)).Count())9CountWhere
Section titled “CountWhere”func (s Seq[T]) CountWhere(pred func(T) bool) intCountWhere returns the number of elements pred admits.
// Fused filter and count: one stage rather than two.statuses := catena.Of(200, 503, 200, 404, 500, 200)fmt.Println(statuses.CountWhere(func(code int) bool { return code >= 500 }))2IsEmpty
Section titled “IsEmpty”func (s Seq[T]) IsEmpty() boolIsEmpty reports whether the sequence yields nothing.
// Answers by consuming one element — on a single-pass source that// element is gone.statuses := catena.Of(200, 204, 301)serverErrors := statuses.Filter(func(code int) bool { return code >= 500 })fmt.Println(serverErrors.IsEmpty(), statuses.IsEmpty())true falseContains
Section titled “Contains”func Contains[T comparable](s Seq[T], v T) boolContains reports whether v occurs in s; stops at the first match.
// Membership by ==, stopping at the first hit.roles := catena.Of("viewer", "editor", "admin")fmt.Println(catena.Contains(roles, "admin"), catena.Contains(roles, "owner"))true falseIndexOf
Section titled “IndexOf”func IndexOf[T comparable](s Seq[T], v T) intIndexOf returns the index of the first occurrence of v; -1 if none.
// The position of the first equal element, or -1.stages := catena.Of("build", "test", "deploy")fmt.Println(catena.IndexOf(stages, "deploy"))fmt.Println(catena.IndexOf(stages, "rollback"))2-1func Equal[T comparable](a, b Seq[T]) boolEqual reports whether a and b yield the same elements in the same order. Consumes both sequences up to and including the first difference — fully when they are equal. b is consumed through iter.Pull (its cleanup always runs).
// Same elements in the same order. A sequence that merely starts// the same way is not equal.expected := catena.Of("001_users", "002_orders", "003_indexes")applied := catena.Of("001_users", "002_orders", "003_indexes")behind := catena.Of("001_users", "002_orders")fmt.Println(catena.Equal(expected, applied))fmt.Println(catena.Equal(expected, behind))truefalse