seqreader

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 15, 2024 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package seqreader is a utility library for representing fallible sequences.

A fallible sequence is a series of values for which an error could potentially occur on any read.

SeqReader is therefore a fallible equivalent of iter.Seq, whose design is inspired by io.Reader. iter.Seq and iter.Seq2 are both designed for infallible sequences and the common patterns using them are not well-suited to fallible sequences.

This package also includes various adapter functions to help with using SeqReader values where iter.Seq or iter.Seq2 values are expected, using iter.Seq values where SeqReader values are expected, and using io.Reader values where SeqReader values are expected.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrEndOfSeq = errors.New("end of sequence")

ErrEndOfSeq is the error returned by [SeqReader.ReadSeq] once the end of a finite sequence has been reached.

Functions

func ToSeq2

func ToSeq2[T any](r SeqReader[T]) iter.Seq2[T, error]

ToSeq2 returns an iter.Seq2 over results from the given SeqReader, where each item in the sequence includes both a result and an error.

Use this to adapt a SeqReader to a situation that requires an iter.Seq2. For example:

for item, err := range seqreader.ToSeq2[seqReader] {
    if err != nil {
        // handle err
    }
    // handle item
}
Example
// This is a SeqReader[string] that fails on reads three and onward.
r := SeqReaderThatFails()
for msg, err := range seqreader.ToSeq2(r) {
	if err != nil {
		fmt.Printf("error: %s\n", err)
		break // this sequence never stops returning errors, so must break
	}
	fmt.Printf("msg = %q\n", msg)
}
Output:

msg = "hello"
msg = "world"
error: failed to ding the wotsit

Types

type FallibleSeq

type FallibleSeq[T any] struct {
	// contains filtered or unexported fields
}

FallibleSeq provides an iter.Seq over items of type T where reads can potentially fail with an error.

After exhausting the sequence, call method Err to determine whether an error occured:

for item := range fallibleSeq.Items() {
    // Do something with item
}
if err := fallibleSeq.Err(); err != nil {
    // Handle err
}

func ToSeq

func ToSeq[T any](r SeqReader[T]) *FallibleSeq[T]

ToSeq adapts the given SeqReader into an object that provides both an iter.Seq and a possible error encountered while reading from it.

This is to allow using a SeqReader with a function that expects iter.Seq, by splitting the iteration responsibility from the error-handling responsibility.

Example
// This is a SeqReader[string] that fails on its third read.
r := SeqReaderThatFails()
messages := seqreader.ToSeq(r)

// Use the result of the Items method with something that expects
// an infallible, finite iter.Seq.
slice := slices.Collect(messages.Items())
// After that operation is complete, call Err to find out if
// the traversal ended early due to an error.
if err := messages.Err(); err != nil {
	fmt.Printf("err = %q\n", err)
}
fmt.Printf("slice = %#v\n", slice)
Output:

err = "failed to ding the wotsit"
slice = []string{"hello", "world"}

func (*FallibleSeq[T]) Err

func (s *FallibleSeq[T]) Err() error

func (*FallibleSeq[T]) Items

func (s *FallibleSeq[T]) Items() iter.Seq[T]

Items returns an iter.Seq over the items in the sequence, which terminates either when the underlying sequence is exhausted or when reading from that sequence returns an error.

Call method Err after the sequence is exhausted to determine whether an error occurred.

type SeqReadCloser

type SeqReadCloser[T any] interface {
	SeqReader[T]
	io.Closer
}

SeqReadCloser is a SeqReader that also implements io.Close, which the caller should call once they are finished reading items from the sequence.

func FromSeq

func FromSeq[T any](seq iter.Seq[T]) SeqReadCloser[T]

FromSeq adapts the given seq into a SeqReadCloser.

Calling Close on the result tells the sequence to terminate.

Example
package main

import (
	"fmt"
	"os"
	"slices"

	"github.com/apparentlymart/go-seqreader/seqreader"
)

func main() {
	seq := slices.Values([]string{"hello", "world"})
	r := seqreader.FromSeq(seq)
	defer r.Close()

	for {
		s, err := r.ReadSeq()
		if err == seqreader.ErrEndOfSeq {
			break
		}
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to read: %s\n", err)
			break
		}
		fmt.Println(s)
	}

}
Output:

hello
world

type SeqReader

type SeqReader[T any] interface {
	// ReadSeq either returns the next item in the sequence or returns an
	// error describing why another item cannot be read.
	//
	// At the end of the sequence the error is [ErrEndOfSeq].
	ReadSeq() (T, error)
}

SeqReader is a sequence of items of type T from which reads can potentially fail.

for {
    item, err := seqReader.ReadSeq()
    if err == ErrEndOfSeq {
        break
    }
    if err != nil {
        // handle err
    }
    // handle item
}

func FromIOReader

func FromIOReader(r io.Reader, makeBuf func() []byte) SeqReader[[]byte]

FromIOReader returns a SeqReader over byte slices returned from the given reader.

The resulting reader needs byte arrays to recieve data from the io.Reader. makeBuf should return a slice whose length is the maximum size of a single read but which may have excess capacity to allow using the remainder of the underlying array for future reads as long as the remaining capacity is at least the original slice length.

The SeqReader returns ErrEndOfSeq when the underlying io.Reader returns io.EOF.

The result dynamically implmenets SeqReadCloser, calling Close on the given reader if it is actually an io.ReadCloser, or a no-op success if not.

Example
package main

import (
	"bytes"
	"fmt"
	"os"

	"github.com/apparentlymart/go-seqreader/seqreader"
)

func main() {
	buf := []byte(`hello, world of fallible sequences!`)
	ioR := bytes.NewReader(buf)
	r := seqreader.FromIOReader(ioR, func() []byte {
		fmt.Println("(allocating buffer)")
		return make([]byte, 4, 16)
	})

	for {
		chunk, err := r.ReadSeq()
		if err == seqreader.ErrEndOfSeq {
			break
		}
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to read: %s\n", err)
			break
		}
		fmt.Printf("chunk: %q\n", chunk)
	}

}
Output:

(allocating buffer)
chunk: "hell"
chunk: "o, w"
chunk: "orld"
chunk: " of "
(allocating buffer)
chunk: "fall"
chunk: "ible"
chunk: " seq"
chunk: "uenc"
(allocating buffer)
chunk: "es!"

func ItemsFromSliceReader

func ItemsFromSliceReader[T any](r SeqReader[[]T]) SeqReader[T]

ItemsFromSliceReader adapts a SeqReader over slices into a SeqReader over individual items from those slices, as if all concatenated together.

For example, this can adapt a SeqReader[[]byte] into a SeqReader[byte], making the sequence of byte slices appear as a flat sequence of bytes.

The given sequence must either have a finite number of items or must eventually produce a non-empty item, or reading from the resulting sequence will fail to terminate.

Example
package main

import (
	"fmt"
	"os"
	"slices"

	"github.com/apparentlymart/go-seqreader/seqreader"
)

func main() {
	seq := slices.Values([][]byte{[]byte("hello "), []byte("world")})
	sliceR := seqreader.FromSeq(seq)
	r := seqreader.ItemsFromSliceReader(sliceR)

	for {
		b, err := r.ReadSeq()
		if err == seqreader.ErrEndOfSeq {
			break
		}
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to read: %s\n", err)
			break
		}
		fmt.Printf("byte: 0x%02x (%c)\n", b, b)
	}

}
Output:

byte: 0x68 (h)
byte: 0x65 (e)
byte: 0x6c (l)
byte: 0x6c (l)
byte: 0x6f (o)
byte: 0x20 ( )
byte: 0x77 (w)
byte: 0x6f (o)
byte: 0x72 (r)
byte: 0x6c (l)
byte: 0x64 (d)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL