spork-pds API

spork-pds 0.1.4

Constructors, operators, protocols, methods, collection families, and transient lifecycle.

The distribution name is spork-pds; the canonical import name is spork.pds. The native spork_pds extension remains importable for compatibility.

import spork.pds as pds

This reference describes the public Python surface. For task-oriented examples and collection-selection advice, see the practical guide.

Module exports#

Factory functions#

FunctionResultNotes
vec(*values)VectorWith one non-string iterable, consumes that iterable. With multiple arguments, treats them as elements.
vec_f64(*values)DoubleVectorConverts positional numeric values to C double.
vec_i64(*values)IntVectorConverts positional integer values to signed 64-bit integers.
hash_map(*key_values)MapAccepts alternating keys and values. The argument count must be even.
hash_set(iterable=None)SetBuilds a set from at most one iterable.
sorted_vec(iterable=None, *, key=None, reverse=False)SortedVectorBuilds an ordered collection; duplicates are retained.
cons(first, rest=None)ConsCreates one immutable linked-list cell.

Types#

Persistent types:

Transient types:

Persistent values should be created through their public classes, factory functions, or constants. Prefer .transient() over constructing transient classes directly; transient constructors are exported primarily so callers can perform type checks.

Empty values#

The vec, vec_f64, vec_i64, hash_map, and hash_set empty factories return their shared empty values. sorted_vec() creates an equivalent empty sorted vector. Empty values are immutable and safe to reuse.

Construction#

The class constructors follow familiar Python collection conventions:

from spork.pds import Map, Set, Vector

vector = Vector(range(3))
map_value = Map({"a": 1}, b=2)
set_value = Set([1, 2, 2])

Vector() and Set() accept at most one iterable. Map() accepts at most one mapping or iterable of pairs, followed by keyword entries. Passing more positional arguments raises TypeError.

Factories provide the library's variadic forms:

from spork.pds import hash_map, hash_set, vec

assert list(vec(1, 2, 3)) == [1, 2, 3]
assert dict(hash_map("a", 1, "b", 2).items()) == {"a": 1, "b": 2}
assert set(hash_set([1, 2, 2])) == {1, 2}

With exactly one non-string iterable, vec consumes that iterable. A string passed to vec is one element; Vector("abc"), by contrast, uses normal iterable-constructor behavior and creates three elements.

hash_map requires alternating key and value arguments, so an odd argument count raises ValueError. hash_set accepts zero or one iterable, not variadic elements.

Common behavior#

All persistent collections are immutable at the collection boundary: an update returns a persistent value and does not expose a way to replace entries in the receiver. This guarantee is shallow; nested Python objects retain their own mutation behavior.

The persistent types support:

TypeCollection ABCGeneric alias exampleHashablePickle
VectorSequenceVector[int]when its elements are hashableyes
DoubleVectorSequenceDoubleVector[float]yesyes
IntVectorSequenceIntVector[int]yesyes
MapMappingMap[str, int]when its keys and values are hashableyes
SetSetSet[str]when its members are hashableyes
SortedVectorSequenceSortedVector[int]when its elements are hashableyes; key must also be picklable
ConsSequenceCons[int]when its elements are hashableyes

Hashability of contents follows normal Python rules. Pickle recreates the same public type and contents, but object identity and preservation of internal structural sharing are not API guarantees. Never unpickle untrusted data.

Persistent operators#

The primary collection operators follow Python's built-in collection vocabulary:

ExpressionResult
vector + iterableConcatenated Vector
vector * count, count * vectorRepeated Vector
map_value | mappingMerged Map; right-hand values win
mapping | map_valueMerged Map; right-hand values win
set_value | other_setUnion as a Set
set_value & other_setIntersection as a Set
set_value - other_setDifference as a Set
set_value ^ other_setSymmetric difference as a Set

Map union operands must be mapping-like and provide an items() method. Set operators accept Set, built-in set, or frozenset; unlike methods such as isdisjoint, binary operators do not accept arbitrary iterables. Built-in set and frozenset values are also supported on the left, and the result is still a persistent Set.

Vector concatenation accepts any iterable on the right. Iterable-left concatenation is intentionally unsupported, so [1] + vector raises TypeError.

No in-place number slots are defined. Augmented assignment therefore computes a persistent result and rebinds the target name:

original = Map({"count": 1})
updated = original
updated |= {"count": 2}

assert original["count"] == 1
assert updated["count"] == 2

Vector#

A general-purpose persistent sequence. Vector() accepts at most one iterable; use vec(*values) when variadic element construction is convenient.

from spork.pds import Vector

original = Vector([10, 20, 30])
appended = original + [40]
repeated = original * 2
updated = original.assoc(1, 99)
shorter = appended.pop()
slice_value = appended[1:3]

Methods:

Python support includes len, iteration, reversed, membership, integer indexing, negative indexing, slicing, + concatenation, * repetition, equality, hashing, collections.abc.Sequence, generic aliases such as Vector[int], and pickle. Concatenation accepts any iterable on the right. Repetition accepts an integer-like count; non-positive counts produce the empty vector.

TransientVector#

Methods:

It also supports length, iteration, membership, indexing, and indexed assignment while editable. Mutating methods return the same transient, which permits explicit method chaining; Python methods such as append should still normally be used for their side effect.

transient = vec(3, 1).transient()
transient.append(2)
transient.sort()
result = transient.persistent()
assert list(result) == [1, 2, 3]

DoubleVector and IntVector#

Specialized persistent sequences store unboxed C values:

Both support len, iteration, indexing, negative indexing, slicing, hashing, nth, conj, transient, generic aliases, pickle, and collections.abc.Sequence. Slicing preserves the specialized vector type.

Their buffers are one-dimensional and read-only:

from spork.pds import vec_f64, vec_i64

floats = vec_f64(1, 2.5, 3)
integers = vec_i64(1, 2, 3)

float_buffer = memoryview(floats)
int_buffer = memoryview(integers)

assert float_buffer.format == "d"
assert int_buffer.format == "q"
assert float_buffer.readonly and int_buffer.readonly

A first buffer request flattens the trie into cached contiguous storage. Further views of the same immutable value reuse that storage.

TransientDoubleVector and TransientIntVector expose conj_mut(value) and persistent(). They are focused builders and do not implement the full mutable-sequence API.

Map#

A persistent hash map.

from spork.pds import Map

original = Map({"a": 1, "b": 2})
updated = original | {"b": 20, "c": 3}
removed = updated.dissoc("a")

assert original["b"] == 2
assert updated["b"] == 20
assert dict(removed.items()) == {"b": 20, "c": 3}

Map() accepts at most one mapping or iterable of key/value pairs, followed by optional keyword entries. Map | Mapping and Mapping | Map use dict-union precedence: values from the right operand win, and the result is always a persistent Map.

Methods:

Maps support len, key iteration, membership, subscription, | merge, equality, hashing, collections.abc.Mapping, generic aliases such as Map[str, int], and pickle.

TransientMap#

Methods:

It also supports the mutable mapping protocols for lookup, assignment, deletion, iteration, membership, and length.

transient = hash_map("a", 1).transient()
transient["b"] = 2
del transient["a"]
result = transient.persistent()

Set#

A persistent hash set.

from spork.pds import Set

left = Set([1, 2, 3])
right = Set([3, 4])

assert set(left | right) == {1, 2, 3, 4}
assert set(left & right) == {3}
assert set(left - right) == {1, 2}
assert set(left ^ right) == {1, 2, 4}

Methods:

Set() accepts at most one iterable. Sets support len, iteration, membership, equality and ordering comparisons, hashing, standard binary set operators, reflected operations with built-in sets and frozensets, collections.abc.Set, generic aliases such as Set[int], and pickle. Binary operators always return a persistent Set.

TransientSet#

Methods:

The transient supports length, membership, and iteration while editable. Its mutating methods return the same transient.

SortedVector#

A persistent ordered collection that retains duplicates and supports O(log n) indexing by rank.

from spork.pds import sorted_vec

values = sorted_vec([3, 1, 2, 2])
assert list(values) == [1, 2, 2, 3]
assert values.first() == 1
assert values.last() == 3
assert values.rank(2) == 1

Methods:

key and reverse are preserved across persistent updates. SortedVector supports length, iteration, indexing, negative indexing, membership, equality, hashing, collections.abc.Sequence, generic aliases, and pickle. A key function must itself be picklable for the collection to be pickled.

TransientSortedVector exposes conj_mut(value), disj_mut(value), and persistent(). It retains the source value's key and reverse policy and is not a general mutable sequence.

Cons#

An immutable linked-list cell with read-only first and rest properties.

from spork.pds import cons

values = cons(1, cons(2, cons(3)))
assert list(values) == [1, 2, 3]
assert values.first == 1
assert values.rest.first == 2

conj(value) prepends a new cell. Cons chains support length, iteration, equality, hashing, collections.abc.Sequence, generic aliases, and pickle.

Transient lifecycle#

All transient variants are single-use builders:

  1. Call .transient() on a persistent value.
  2. Apply mutable operations.
  3. Call .persistent() once.
  4. Discard the transient.

A transient is invalid after step 3. Calling any read or edit operation on it raises RuntimeError. Mutating operations return the same transient object; .persistent() returns the persistent result.

TransientVector, TransientMap, and TransientSet implement MutableSequence, MutableMapping, and MutableSet, respectively. Typed-vector and sorted-vector transients expose only their documented focused methods.

A transient is not a general-purpose mutable collection and should not escape the batch-update scope. It is single-owner state and must not be shared across threads.