AnnouncementsIntroducing Arkoren

    Menu

    Overview

    Shape in-memory application records with eager slice and map collections while preserving clear ordering and ownership.

    What's on this page
    1. Filter and project typed records, then group or aggregate the selected values.
    2. Choose slices for ordered items and maps for keyed values and explicit overrides.
    3. Distinguish allocated results from shared windows and preserve native result boundaries.
    On this page

    A dispatch report often needs a subset of orders, their identifiers, and a total for each warehouse. The collection package connects those operations as typed Go calls. An eager Slice[T] holds ordered items; an eager Map[K, V] holds values addressed by comparable keys. Their operations run when you call them.

    Use collections to reshape values your application already has. They do not translate callbacks into SQL or fetch more data. Apply database selection and ordering before loading records, then shape the returned values in Go. For one operation, a loop or the standard-library slices and maps packages may be clearer than a chain.

    Creating Collections

    The standalone module requires Go 1.27 and works without the rest of Arkoren. Install it from your application’s module directory:

    go get arkoren.dev/foundation/collection

    Import arkoren.dev/foundation/collection. For a dispatch report, wrap the orders already loaded by your application:

    type Order struct {
    	ID        string
    	Warehouse string
    	Units     int
    	Ready     bool
    }
    
    orders := collection.NewSlice([]Order{
    	{ID: "ord-104", Warehouse: "north", Units: 3, Ready: true},
    	{ID: "ord-105", Warehouse: "south", Units: 2, Ready: false},
    	{ID: "ord-106", Warehouse: "north", Units: 5, Ready: true},
    })

    NewSlice wraps without copying. Slice[T] is a defined []T, so you can index it and range over it directly. Items() returns the underlying []T for standard-library calls. Both the wrapper and that slice share storage. NewSliceAs accepts a named slice type; ItemsAs converts back to one.

    Selecting Orders

    Keep the ready orders before projecting their IDs. Slice callbacks receive the input index and item; _ makes an unused index explicit:

    ready := orders.Filter(func(_ int, order Order) bool {
    	return order.Ready
    })
    ids := ready.Map(func(_ int, order Order) string {
    	return order.ID
    })

    ids is a collection.Slice[string] containing ord-104 and ord-106, in that order. Filter and Map allocate result storage and leave orders unchanged. Methods can introduce a new type parameter, so projection changes the collection’s element type without losing the chain.

    If you need one order rather than another collection, use a lookup:

    order, found := orders.FirstWhere(func(_ int, order Order) bool {
    	return order.Warehouse == "south"
    })

    Here found is true and order.ID is ord-105. If nothing matches, the result is the zero Order and false; checking found distinguishes absence from a stored zero value. First and Last use the same value-plus-boolean convention.

    Summarizing Dispatch

    Group ready orders when each warehouse needs its own dispatch list:

    warehouses := ready.GroupBy(func(_ int, order Order) string {
    	return order.Warehouse
    })

    warehouses["north"] contains the two ready orders in source order. The outer map has no iteration-order guarantee. Use CountBy for the number of orders per warehouse, or KeyBy for one order per identifier; repeated KeyBy keys keep the last item.

    To count units rather than orders, fold the selected records with Reduce:

    units := ready.Reduce(func(total, _ int, order Order) int {
    	return total + order.Units
    }, 0)

    units is 8. The initial value is also the result for empty input, so an empty dispatch has zero units without a separate branch.

    Working With Maps

    Choose a map when the key, rather than a position, identifies each value. Map callbacks receive (key, value). For example, select warehouses with available packing capacity:

    capacity := collection.NewMap(map[string]int{"north": 12, "south": 0})
    available := capacity.Filter(func(_ string, slots int) bool {
    	return slots > 0
    })

    available contains only north: 12. Get reports both a value and whether its key exists; a stored zero is different from an absent key. MapValues changes values while retaining keys, and MapKeys changes keys while retaining values. Colliding transformed keys overwrite earlier entries. With a Go-map source, the winner is unspecified because traversal order is unspecified.

    For explicit overrides, Merge gives the argument precedence regardless of iteration order:

    adjusted := capacity.Merge(collection.NewMap(map[string]int{"south": 4}))

    adjusted["south"] is 4; capacity["south"] remains 0. The result has independent outer map storage. Keep the returned map: Merge does not modify its receiver.

    To display keys in a repeatable order, import strings and sort them:

    names := collection.Keys(capacity).Sort(strings.Compare)

    names contains north, then south. Keys and Values are package functions, not eager-map methods. Map traversal remains unordered; sorting the extracted keys does not reorder the map.

    Copies And Windows

    Wrapping and extracting do not copy. Assigning orders[0] changes the slice passed to NewSlice, and writes through orders.Items() change the collection. Allocated transformations such as Filter, Map, Sort, Reverse, and Merge own their outer result storage. Copies remain shallow: pointers, maps, and slices inside elements can still refer to shared data.

    Slice windows behave differently. Take, Skip, SkipWhile, SkipUntil, and ForPage share the source array, as do eager Chunk and Sliding windows. A write through a window can change the source; retaining a small window can also keep a large array alive. To keep an independent dispatch prefix, import the standard-library slices package and clone it:

    batch := collection.NewSlice(slices.Clone(ready.Take(1).Items()))
    batch[0].Units = 1

    ready[0].Units is still 3. Sort already makes a shallow copy, but it is not stable: equal items need not retain their relative order. Supply a comparison that includes a tie-breaker if that distinction matters.

    Nil eager collections support empty reads. First reports false, Len is zero, and a filter with no matches returns nil slice storage. Constructors and Items preserve nil versus allocated-empty input; windows preserve their source representation. A nil map still panics on indexed writes: allocate it first, or keep a populated Merge result. Concurrent access follows ordinary Go slice and map rules; synchronize writes and shared mutable elements.

    Choosing Evaluation

    Eager operations suit data already held in memory. Use Lazy Collections when callbacks should run only as results are consumed, especially when you need only a prefix. Use Fallible Collections when a source or callback can fail during traversal. Neither conversion copies the underlying application data or supplies cancellation automatically.

    For selection, windowing, and serialization details, use Collection Methods. Validate untrusted window sizes: Chunk and Sliding require positive sizes, and Nth and Sliding require positive steps. Invalid values panic. Take and Skip clamp counts instead; ForPage does not validate page inputs or arithmetic overflow.

    Native Results

    Arkoren data results can already be collections. Database Select returns an eager slice; cursors and batch traversal return fallible lazy slices. Paginated results contain only the returned window: filtering its Items does not change the server’s total, query ordering, or navigation tokens.

    Inspection results, including router routes, scheduler entries, telemetry records, and workflow definitions, retain their producer’s ordering and copy depth. The collection wrapper adds no extra isolation. Storage listings and model streams retain their producer’s cleanup and replay rules; traversing a model source again can repeat billed work.

    Eager collections use their native container’s JSON shape through encoding/json/v2. Default nil non-byte slices encode as [], maps as {}, and byte slices as base64 strings. Lazy JSON encoding materializes the source; it is not network streaming. For schemas, eager collections declare their native shape to output description. Attach logical rules through the named-container adapter.

    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