Skip to content
Pyrula
Pre-release: the packages are not on PyPI yet, so pip install does not resolve. Build from source to try it today.

IList and IMap

IList and IMap never change in place. Every operation returns a new collection that shares structure with the source, so they’re safe across threads.

Iteration and map/filter run a few times slower than list comprehensions. For hot numeric loops use the numeric lists.

from pyrula import IList
nums = IList([1, 2, 3, 4])
nums.map(lambda n: n * 2) # IList([2, 4, 6, 8])
nums.filter(lambda n: n % 2 == 0) # IList([2, 4])
nums.fold_left(0, lambda acc, n: acc + n) # 10
nums.append(5) # new IList; nums is still length 4

Heads and lookups return an Option:

nums.head() # Some(1)
IList([]).head() # Nothing
nums.find(lambda n: n > 2) # Some(3)

An IList of Either splits without a loop:

results.collect_ok() # IList of the Ok values
results.collect_err() # IList of the Err values
results.partition_either() # (IList[ok], IList[err])
from pyrula import IMap
m = IMap({"a": 1, "b": 2})
m["a"] # 1 (KeyError if missing)
m.get("z", 0) # 0
m.get_option("a") # Some(1)
m.set("c", 3) # new IMap; m still has two keys
m.remove("a") # new IMap without "a"
"b" in m # True

IMap is a collections.abc.Mapping; IMap(x) takes a mapping or (key, value) pairs. Also map_values, filter_values, merge, to_dict.

Keys are matched by Python hash() and ==, as in a dict.

IMap and ISet iterate in an order that depends only on their contents: equal collections iterate identically in any process and under any PYTHONHASHSEED. It is neither insertion nor sorted order. This holds for keys that are exact str, bytes, int, float, bool, None, or tuples and frozensets of those. Other key types (plain objects, Enum members, str subclasses with their own __hash__) can iterate differently between processes. Sort if the order matters.

Collections and case classes (Ok, Err, Some, Success, Failure, Valid, Duration) compare by value like the nearest builtin.

  • IList, NonEmptyList, IntList, FloatList and BytesList act like tuple: equal only to the same class, so IList([1]) != [1].
  • ISet and IntSet act like frozenset: ISet([1]) == frozenset({1}) == IntSet([1]).
  • IMap acts like a read-only mapping: IMap({"a": 1}) == {"a": 1}.

A value is hashable exactly when its elements are. Everything pickles and deep-copies, so values cross a ProcessPoolExecutor or par_map(..., result_codec="pickle").

Hashing, comparing, ordering and formatting recurse in native code. A value nested more than 800 levels deep raises RecursionError instead of crashing, and sys.setrecursionlimit does not change that limit. Threads with small stacks (512 KiB for non-main threads on macOS) can overflow earlier, so keep values shallow there.

View this page as Markdown