# IList and IMap

> Immutable list and map. Every operation returns a new collection.

Source: https://docs.pyrula.io/primitives/collections/

`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](https://docs.pyrula.io/primitives/numeric-lists.md).

## IList

```python
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`](https://docs.pyrula.io/primitives/option.md):

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

An `IList` of [`Either`](https://docs.pyrula.io/primitives/either.md) splits without a loop:

```python
results.collect_ok()        # IList of the Ok values
results.collect_err()       # IList of the Err values
results.partition_either()  # (IList[ok], IList[err])
```

## IMap

```python
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`.

## Iteration order

`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.

## Equality and hashing

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")`.

### Nesting depth

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.
