AnnouncementsIntroducing Arkoren

    Menu

    Fallible Collections

    Preserve read and callback failures while choosing whether to stop, retain a partial result, or continue traversal.

    What's on this page
    1. Choose first-error or continuing traversal without treating partial successes as complete input.
    2. Track successful positions while preserving source and callback errors.
    3. Expose reader failures and retain producer-owned cleanup, cancellation, and replay constraints.
    On this page

    An import can fail after several records have succeeded. TryLazySlice[T] yields (item, error) pairs so that failure remains part of the traversal. TryLazyMap[K, V] yields (MapEntry[K, V], error) pairs, where MapEntry contains Key and Value. Ignore the item or entry whenever its error is non-nil.

    These are lazy iterators, not saved results. Streaming transformations forward source and callback errors; the consumer decides whether to stop or continue. Ordinary terminal methods stop on the first error. Methods ending in All continue while the source permits it and return joined errors. Neither policy retries a failed operation or recovers a panic.

    Parsing Import Values

    Import arkoren.dev/foundation/collection and the standard-library strconv package. Start with typed import lines so that source identity is available even if callback indexes change:

    type QuantityLine struct {
    	Line  int
    	Units string
    }
    
    lines := collection.NewSlice([]QuantityLine{
    	{Line: 1, Units: "3"},
    	{Line: 2, Units: "unknown"},
    	{Line: 3, Units: "5"},
    })

    Try() converts an eager or infallible lazy collection to its fallible lazy form. It does not parse anything yet. Make parsing a step that can return an error:

    quantities := lines.Try().Map(func(_ int, line QuantityLine) (int, error) {
    	return strconv.Atoi(line.Units)
    })

    The result is a TryLazySlice[int]. When consumed, successful conversions yield an integer and nil error; the invalid line yields a conversion error instead of a usable integer. A source error bypasses the callback entirely.

    Stopping On Failure

    Choose Items when a failure should stop this read:

    units, err := quantities.Items()

    units contains only [3], and err is the conversion error for unknown. The third line is not processed. Decide whether your application can use that prefix or must reject the import; it is not a complete result. Eager returns an eager collection with the same first-error policy. A slice materializer returns nil if no values succeed; map materializers return allocated maps. Both own new outer storage with shallow element copies.

    Each also stops on the first source or callback error. Work completed by an earlier callback is not rolled back. If callbacks write application data, use your application’s transaction or idempotency policy rather than assuming collection traversal is atomic.

    Continuing Explicitly

    If independent import lines may succeed despite other invalid lines, choose ItemsAll instead. This is an alternative traversal of the replayable in-memory source above, not a second pass over an already consumed reader:

    units, err := quantities.ItemsAll()

    units contains [3, 5]. err joins the encountered errors with errors.Join; it is still non-nil even though later values succeeded. EagerAll collects into an eager collection, and EachAll continues after both source and callback errors. Use errors.Is or errors.As to inspect a joined cause when needed.

    For a per-error decision, range over the fallible source. This loop deliberately reports each conversion failure and skips its accompanying value; import fmt for the output:

    for units, err := range quantities {
    	if err != nil {
    		fmt.Printf("Rejected quantity: %v\n", err)
    		continue
    	}
    	fmt.Println(units)
    }

    The loop prints 3, reports the invalid quantity, then prints 5. Replacing continue with break stops the source at that error. Continuation moves to the next source result; it does not retry the failed line. A terminal read error can end a source even when the consumer wants to continue.

    Limits And Indexes

    Take and Skip count successful values or map entries, not errors. Errors are forwarded in source order, including inside a skipped prefix. A positive Take stops immediately after its last requested success. Take(0) and negative limits consume nothing, so they cannot reveal any source error. Duplicate lazy-map keys count as separate entries, even though materialization keeps only the last successful value for a key.

    Nth, Chunk, and Sliding likewise count successful slice values. An error does not consume an offset, a step position, or space in a partial window. Continuing after the error resumes that progress if the source has more results; ignoring the error would conceal an incomplete or invalid input. Sizes and steps must be positive where required, as described in Collection Methods.

    Streaming slice callbacks start at index zero. Source errors do not advance the index, and callback errors leave the next callback at the same index. For the three import lines, this records that difference:

    var indexes []int
    parsed := lines.Try().Map(func(index int, line QuantityLine) (int, error) {
    	indexes = append(indexes, index)
    	return strconv.Atoi(line.Units)
    })
    units, err := parsed.ItemsAll()

    indexes contains [0, 1, 1], units contains [3, 5], and err is non-nil. A successful predicate still advances its input index when it rejects a value; a successful FlatMap advances once even when it returns no items. Later steps have their own input indexes. Carry QuantityLine.Line into application errors or output records when you need the original line number.

    EachAll deliberately differs: its index counts every successful source value, including a value whose callback fails. Calling it directly on lines.Try() therefore gives callback indexes zero, one, and two. Source errors still bypass its callback and do not advance that index.

    Supplying Read Errors

    For an existing application-owned io.Reader named reader, a scanner-backed iterator must expose Scanner.Err, not silently treat a failed read as the end of input. Import bufio along with collection:

    text := collection.NewTryLazySlice(func(yield func(string, error) bool) {
    	scanner := bufio.NewScanner(reader)
    	for scanner.Scan() {
    		if !yield(scanner.Text(), nil) {
    			return
    		}
    	}
    	if err := scanner.Err(); err != nil {
    		yield("", err)
    	}
    })

    text.Items() returns scanned lines and any read or scanner-limit error. The source returns immediately when yield is false. Its last error yield needs no continuation check because the iterator returns immediately afterward.

    The reader remains application-owned: wrapping it does not close it or cancel a blocked read. If your iterator opens a resource, arrange its release inside the iterator on every return, including early stop. If it captures an already open resource, its existing owner arranges cleanup. Blocking sources must observe their own context; the collection supplies no cancellation or timeout. Use a non-nil iterator function, as explained under Supplying An Iterator.

    This reader-backed source is single-use unless the reader itself supports replay. Materialize once when you need to inspect the same values repeatedly. Do not call ItemsAll after Items expecting it to resume with every original line: each traversal invokes a new scanner over the reader’s current position, and buffering may already have read ahead.

    Native Producers

    Database cursors and batch traversal, storage listings, and model streams return fallible lazy data. Their producers own cleanup, cancellation, and repeat-traversal behavior. A storage listing’s page size is not a total-item limit; repeated traversal can issue fresh requests. Repeating a model stream can repeat billed work, and stopping early interrupts execution rather than completing a model turn.

    These producers stop after terminal read or execution errors. ItemsAll can continue after your transformation fails, but cannot reconnect a cursor, retry a failed page fetch, or resume a failed model request. Check the returned error before treating a collected prefix as complete.

    Full-input transformations have their own failure shapes: Sort and Reverse discard a failed materialization’s prefix and yield only the error. Partition consumes at the call and makes both returned iterators yield the error without partial partitions. GroupBy, KeyBy, and CountBy return partial eager maps with the first error. JSON encoding returns the first source error instead of encoding a partial collection; materialization is not network streaming.

    For exact declarations, see the collection API reference.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive