Skip to content

Interoperability & Chaining Guide

Pyochain is designed for fluent API usage. Most types can be converted into others seamlessly, allowing you to chain operations without breaking the flow.

Shared Features

To enable this flexibility, Pyochain provides two core traits (mixins) that extend all types with powerful chaining and conditional logic capabilities.

Pipeable trait

All pyochain types implement Pipeable, providing universal methods to continue a chain:

.into(func, *args, **kwargs) -> R

Convert Self to any type R via a function, maintaining fluent chaining.

Conceptually, this replaces f(x, args, kwargs) with x.into(f, args, kwargs).

This is particularly useful when you need to pass the result to a function you don't control (like a library function), or to convert to a type not native to Pyochain.

>>> import json
>>> import pyochain as pc
>>> 
>>> # Flow is broken, nested function calls, read from middle -> right -> left -> right
>>> nested = json.dumps(
...     dict(
...         pc.Dict({"id": 1, "name": "Alice"})
...         .items()
...         .iter()
...         .map_star(lambda k, v: (k.upper(), v))
...     )
... )
>>> nested
'{"ID": 1, "NAME": "Alice"}'
>>> # Fluent chaining with .into(), read left -> right
>>> fluent = (
...     pc.Dict({"id": 1, "name": "Alice"})
...     .items()
...     .iter()
...     .map_star(lambda k, v: (k.upper(), v))
...     .into(lambda d: json.dumps(dict(d)))
... )
>>> fluent
'{"ID": 1, "NAME": "Alice"}'

.inspect(func, *args, **kwargs) -> Self

Pass Self to a function for side effects (logging, debugging, metrics) without breaking the chain. The instance is returned unchanged.

>>> import pyochain as pc
>>> pc.Seq([1, 2, 3]).inspect(print).iter().map(lambda x: x * 2).collect()
Seq(1, 2, 3)
Seq(2, 4, 6)

Checkable trait

Collections (Seq, Vec, Set, SetMut, Dict) and iterators (Iter) implement Checkable, providing conditional chaining based on truthiness (usually emptiness):

.then(func, *args, **kwargs) -> Option[R]

Call func and wrap result in Some only if the instance is truthy.

>>> import pyochain as pc
>>> pc.Seq([1, 2, 3]).then(lambda s: s.sum())
Some(6)
>>> pc.Seq([]).then(lambda s: s.sum())
NONE

.then_some() -> Option[Self]

Wrap the instance in Some if truthy, otherwise NONE.

>>> import pyochain as pc
>>> pc.Seq([1, 2, 3]).then_some()
Some(Seq(1, 2, 3))
>>> pc.Seq([]).then_some()
NONE

.ok_or(err) -> Result[Self, E]

Wrap in Ok if truthy, otherwise wrap the error in Err.

>>> import pyochain as pc
>>> pc.Seq([1, 2, 3]).ok_or("empty list")
Ok(Seq(1, 2, 3))
>>> pc.Seq([]).ok_or("empty list")
Err('empty list')

.ok_or_else(func, *args, **kwargs) -> Result[Self, E]

Wrap in Ok if truthy, otherwise call func and wrap result in Err (lazy evaluation).

>>> import pyochain as pc
>>> pc.Seq([1, 2, 3]).ok_or_else(lambda _: "empty")
Ok(Seq(1, 2, 3))
>>> pc.Seq([]).ok_or_else(lambda _: "empty")
Err('empty')

PyoIterable & PyoCollection traits

PyoIterable[T] and PyoCollection[T] are the foundational traits for all collection and iterator types in Pyochain.

PyoIterable[T] extends Pipeable, Checkable, and Python's Iterable[T] protocol:

  • Provides the base interface shared by all pyochain types (both lazy iterators and eager collections)
  • Concrete types only need to implement __iter__() to satisfy the Iterable[T] protocol
  • Adds common methods like .length(), .sum(), .min(), .max(), .all(), .any(), etc.

PyoCollection[T] extends PyoIterable[T] and Python's Collection[T] protocol:

  • Represents eager (in-memory) collections: Seq, Vec, Set, SetMut, Dict, and mapping views
  • Concrete types must implement __contains__(), __iter__(), and __len__() to satisfy the protocol
  • Iter[T] (lazy iterator) does not extend this trait, only PyoIterator[T]

from_ref() for mutable collections

All mutable collection types (Vec, SetMut, Dict) provide a from_ref() class method that creates a pyochain instance from a reference to an existing Python collection without copying.

Examples

>>> import pyochain as pc
>>> # Vec from list
>>> py_list = [1, 2, 3]
>>> vec = pc.Vec.from_ref(py_list)
>>> vec.append(4)
>>> vec
Vec(1, 2, 3, 4)
>>> py_list  # original list is modified
[1, 2, 3, 4]
>>> 
>>> # SetMut from set
>>> py_set = {1, 2, 3}
>>> set_mut = pc.SetMut.from_ref(py_set)
>>> set_mut.add(4)
>>> set_mut
SetMut(1, 2, 3, 4)
>>> py_set  # original set is modified
{1, 2, 3, 4}
>>> 
>>> # Dict from dict
>>> py_dict = {"a": 1, "b": 2}
>>> pyo_dict = pc.Dict.from_ref(py_dict)
>>> pyo_dict.insert("c", 3)
NONE
>>> pyo_dict
Dict('a': 1, 'b': 2, 'c': 3)
>>> py_dict  # original dict is modified
{'a': 1, 'b': 2, 'c': 3}

This allows fast, efficient no-copy conversions when dealing with external functions that return standard Python collections.

Note: Immutable collections (Seq, Set) don't need from_ref() since their underlying types (tuple, frozenset) are already immutable and Python optimizes their creation automatically.

Conversion & Interoperability Map

The following graph illustrates all the built-in ways to convert between types in Pyochain.

  • Types are grouped by category.
  • Arrows color and direction represent conversion paths.
  • Arrow labels represent methods.
---
config:
  layout: elk
---
flowchart TB
 subgraph Collections["📦 Collections (Eager)"]
    direction LR
        Seq["<b>Seq[T]</b><br>immutable<br>tuple"]
        Vec["<b>Vec[T]</b><br>mutable<br>list"]
        Set["<b>Set[T]</b><br>immutable<br>frozenset"]
        SetMut["<b>SetMut[T]</b><br>mutable<br>set"]
  end
 subgraph Lazy["⛓️ Lazy"]
    direction LR
        Iter["<b>Iter[T]</b><br>lazy iterator<br>Iterator"]
  end
 subgraph DictGroup["🔑 Dictionary"]
    direction LR
        Dict["<b>Dict[K,V]</b><br>mutable<br>dict"]
  end
 subgraph OptionGroup["🎁 Option Types"]
    direction LR
        Option["<b>Option[T]</b>"]
        Some["<b>Some[T]</b>"]
        NONE["<b>NONE</b>"]
  end
 subgraph ResultGroup["✅ Result Types"]
    direction LR
        Result["<b>Result[T,E]</b>"]
        Ok["<b>Ok[T]</b>"]
        Err["<b>Err[E]</b>"]
  end
 subgraph External["🌐 External Types"]
    direction LR
        AnyType["<b>Any Type</b><br>via .into(func)"]
  end
    Option -.-> Some & NONE
    Result -.-> Ok & Err
    Collections -- ".iter()" --> Lazy
    Lazy -- ".collect()" --> Collections
    Lazy -- ".collect(Dict)" --> DictGroup
    DictGroup -- ".iter() → Item[K,V]<br>.keys_iter() → K<br>.values_iter() → V" --> Lazy
    OptionGroup -- ".iter()" --> Lazy
    ResultGroup -- ".iter()" --> Lazy
    DictGroup -- ".get_item(key)<br>.insert(key, val)<br>.remove(key)" --> OptionGroup
    DictGroup -- ".try_insert(key, val)" --> ResultGroup
    Collections -- ".then(func)<br>.then_some()" --> OptionGroup
    Lazy -- ".then(func)<br>.then_some()" --> OptionGroup
    DictGroup -- ".then(func)<br>.then_some()" --> OptionGroup
    Collections -- ".ok_or(err)<br>.ok_or_else(func)" --> ResultGroup
    Lazy -- ".ok_or(err)<br>.ok_or_else(func)" --> ResultGroup
    DictGroup -- ".ok_or(err)<br>.ok_or_else(func)" --> ResultGroup
    OptionGroup -- ".ok_or(err)<br>.ok_or_else(func)" --> ResultGroup
    ResultGroup -- ".ok()<br>.err()" --> OptionGroup
    OptionGroup L_OptionGroup_ResultGroup_2@<-- ".transpose()" --> ResultGroup
    Collections -- ".into(func)" --> External
    Lazy -- ".into(func)" --> External
    DictGroup -- ".into(func)" --> External
    OptionGroup -- ".into(func)" --> External
    ResultGroup -- ".into(func)" --> External

     Seq:::collectionsStyle
     Vec:::collectionsStyle
     Set:::collectionsStyle
     SetMut:::collectionsStyle
     Iter:::iterStyle
     Dict:::dictStyle
     Option:::optionStyle
     Some:::optionStyle
     NONE:::optionStyle
     Result:::resultStyle
     Ok:::resultStyle
     Err:::resultStyle
     AnyType:::externalStyle
     Collections:::collectionsStyle
     OptionGroup:::optionStyle
     ResultGroup:::resultStyle
    classDef collectionsStyle fill:#1e88e5,stroke:#0d47a1,stroke-width:2px,color:#fff
    classDef iterStyle fill:#43a047,stroke:#1b5e20,stroke-width:2px,color:#fff
    classDef dictStyle fill:#fb8c00,stroke:#e65100,stroke-width:2px,color:#fff
    classDef optionStyle fill:#fdd835,stroke:#f57f17,stroke-width:2px,color:#000
    classDef resultStyle fill:#e53935,stroke:#b71c1c,stroke-width:2px,color:#fff
    classDef externalStyle fill:#9e9e9e,stroke:#424242,stroke-width:2px,color:#fff
    style Seq color:none
    style Vec color:none
    style Set color:none
    style SetMut color:none
    style Dict fill:#FF6D00,color:none,stroke:#FF6D00
    style Option color:#FFFFFF,fill:transparent,stroke:#FFD600
    style Some color:#FFFFFF,fill:transparent,stroke:#FFD600
    style NONE color:#FFFFFF,fill:transparent,stroke:#FFD600
    style Result fill:#D50000
    style Ok fill:#D50000
    style Err fill:#D50000
    style Collections fill:#000000,color:none,stroke:#2962FF
    style Lazy fill:#000000,stroke:#00C853
    style DictGroup fill:#000000,color:none,stroke:#FF6D00
    style OptionGroup fill:#000000,color:#FFFFFF,stroke:#FFD600
    style ResultGroup fill:#000000,stroke:#D50000
    style External fill:#000000
    linkStyle 0 stroke:#666,stroke-width:1px,stroke-dasharray:3,fill:none
    linkStyle 1 stroke:#666,stroke-width:1px,stroke-dasharray:3,fill:none
    linkStyle 2 stroke:#666,stroke-width:1px,stroke-dasharray:3,fill:none
    linkStyle 3 stroke:#666,stroke-width:1px,stroke-dasharray:3,fill:none
    linkStyle 4 stroke:#1e88e5,stroke-width:2.5px,fill:none
    linkStyle 5 stroke:#43a047,stroke-width:2.5px,fill:none
    linkStyle 6 stroke:#43a047,stroke-width:2.5px,fill:none
    linkStyle 7 stroke:#fb8c00,stroke-width:2.5px,fill:none
    linkStyle 8 stroke:#fdd835,stroke-width:2.5px,fill:none
    linkStyle 9 stroke:#e53935,stroke-width:2.5px,fill:none
    linkStyle 10 stroke:#fb8c00,stroke-width:2.5px,fill:none
    linkStyle 11 stroke:#fb8c00,stroke-width:2.5px,fill:none
    linkStyle 12 stroke:#1e88e5,stroke-width:2.5px,fill:none
    linkStyle 13 stroke:#43a047,stroke-width:2.5px,fill:none
    linkStyle 14 stroke:#fb8c00,stroke-width:2.5px,fill:none
    linkStyle 15 stroke:#1e88e5,stroke-width:2.5px,fill:none
    linkStyle 16 stroke:#43a047,stroke-width:2.5px,fill:none
    linkStyle 17 stroke:#fb8c00,stroke-width:2.5px,fill:none
    linkStyle 18 stroke:#fdd835,stroke-width:2.5px,fill:none
    linkStyle 19 stroke:#e53935,stroke-width:2.5px,fill:none
    linkStyle 20 stroke:#9c27b0,stroke-width:2.5px,fill:none
    linkStyle 21 stroke:#1e88e5,stroke-width:2.5px,fill:none
    linkStyle 22 stroke:#43a047,stroke-width:2.5px,fill:none
    linkStyle 23 stroke:#fb8c00,stroke-width:2.5px,fill:none
    linkStyle 24 stroke:#fdd835,stroke-width:2.5px,fill:none
    linkStyle 25 stroke:#e53935,stroke-width:2.5px,fill:none

    L_OptionGroup_ResultGroup_2@{ animation: none }