A lens library for python that enables immutable manipulation of deeply nested data structures
—
Low-level optics system providing the mathematical foundation for lens operations. The optics system implements different types of optics (Lens, Prism, Traversal, Isomorphism, etc.) that can be composed to create complex data access patterns.
Core interfaces and base classes that define the optics system.
class LensLike:
"""Base interface for all optic types."""
def kind(self): ...
def compose(self, other): ...
def view(self, state): ...
def over(self, state, func): ...
def set(self, state, value): ...
def to_list_of(self, state): ...
class TrivialIso:
"""Identity isomorphism - does nothing."""
pass
class ComposedLens:
"""Composed optic combining two optics."""
def __init__(self, lens1, lens2): ...
class Equality:
"""Equality-based optic."""
pass
class TupleOptic:
"""Combines multiple optic focuses into a tuple."""
def __init__(self, *optics): ...Different optic types providing various levels of access capability.
class Fold:
"""Read-only access to multiple foci."""
def __init__(self, func: Callable[[A], Iterable[X]]): ...
class Getter:
"""Read-only access to single focus."""
def __init__(self, getter: Callable[[A], X]): ...
class Setter:
"""Write-only access to foci."""
pass
class Review:
"""Write-only optic for constructing values."""
pass
class Traversal:
"""Read-write access to multiple foci."""
def __init__(self, folder: Callable[[A], Iterable[X]],
builder: Callable[[A, Iterable[Y]], B]): ...
class Lens:
"""Read-write access to single focus."""
def __init__(self, getter: Callable[[A], X],
setter: Callable[[A, Y], B]): ...
class Prism:
"""Optional read-write access (zero or one focus)."""
def __init__(self, unpack: Callable[[A], Just[X]],
pack: Callable[[Y], B]): ...
class Isomorphism:
"""Bidirectional conversion between types."""
def __init__(self, forwards: Callable[[A], X],
backwards: Callable[[Y], B]): ...
def re(self): ... # Reverse the isomorphismSpecific fold implementations for read-only traversal.
class IterableFold:
"""Fold over any iterable object."""
passfrom lenses.optics import IterableFold
from lenses import lens
# Direct optic usage (advanced)
fold = IterableFold()
data = [1, 2, 3, 4, 5]
values = fold.to_list_of(data) # [1, 2, 3, 4, 5]
# More commonly used through lens interface
lens.Iter().collect()(data) # [1, 2, 3, 4, 5]Isomorphisms for converting between different data representations.
class DecodeIso:
"""String/bytes encoding conversion isomorphism."""
def __init__(self, encoding: str = "utf-8", errors: str = "strict"): ...
class JsonIso:
"""JSON string parsing isomorphism."""
pass
class NormalisingIso:
"""Value normalization isomorphism."""
def __init__(self, setter: Callable[[A], X]): ...
class ErrorIso:
"""Exception-raising isomorphism for debugging."""
def __init__(self, exception: Exception, message: Optional[str] = None): ...from lenses.optics import JsonIso, DecodeIso
from lenses import lens
# JSON isomorphism
json_iso = JsonIso()
json_data = '{"name": "Alice", "age": 30}'
parsed = json_iso.view(json_data) # {"name": "Alice", "age": 30}
# Through lens interface (more common)
name = lens.Json()["name"].get()(json_data) # "Alice"
# Decode isomorphism
decode_iso = DecodeIso("utf-8")
byte_data = b"hello"
text = decode_iso.view(byte_data) # "hello"Prisms for optional or conditional access patterns.
class FilteringPrism:
"""Prism that filters by predicate."""
def __init__(self, predicate: Callable[[A], bool]): ...
class InstancePrism:
"""Prism that filters by type."""
def __init__(self, type_: Type): ...
class JustPrism:
"""Prism for Maybe Just values."""
passfrom lenses.optics import FilteringPrism, InstancePrism
from lenses import lens
# Filtering prism
positive_prism = FilteringPrism(lambda x: x > 0)
data = [-1, 2, -3, 4]
positive_values = [positive_prism.to_list_of([x]) for x in data] # [[], [2], [], [4]]
# Through lens interface (more common)
positive = lens.Each().Filter(lambda x: x > 0).collect()(data) # [2, 4]
# Instance prism for type filtering
str_prism = InstancePrism(str)
mixed = [1, "hello", 2.5, "world"]
strings = lens.Each().Instance(str).collect()(mixed) # ["hello", "world"]Setters for write-only operations.
class ForkedSetter:
"""Parallel composition of multiple setters."""
def __init__(self, *setters): ...from lenses.optics import ForkedSetter, GetitemLens
from lenses import lens
# Forked setter for parallel updates
fork = ForkedSetter(GetitemLens(0), GetitemLens(2))
data = [1, 2, 3, 4, 5]
updated = fork.set(data, 99) # [99, 2, 99, 4, 5]
# Through lens interface (more common)
fork_lens = lens.Fork(lens[0], lens[2])
result = fork_lens.set(99)(data) # [99, 2, 99, 4, 5]Traversals for read-write access to multiple foci.
class EachTraversal:
"""Traverse all items in a collection."""
pass
class ItemsTraversal:
"""Traverse dictionary items as (key, value) pairs."""
pass
class RecurTraversal:
"""Recursively traverse objects of a specific type."""
def __init__(self, cls): ...
class RegexTraversal:
"""Traverse string parts matching a regex pattern."""
def __init__(self, pattern: Pattern, flags: int = 0): ...
class GetZoomAttrTraversal:
"""Traverse attribute, zooming if it's a lens."""
def __init__(self, name: str): ...
class ZoomAttrTraversal:
"""Traverse lens attribute."""
def __init__(self, name: str): ...
class ZoomTraversal:
"""Follow bound lens objects."""
passfrom lenses.optics import EachTraversal, RecurTraversal
from lenses import lens
# Each traversal
each = EachTraversal()
data = [1, 2, 3]
values = each.to_list_of(data) # [1, 2, 3]
doubled = each.over(data, lambda x: x * 2) # [2, 4, 6]
# Recursive traversal
recur = RecurTraversal(int)
nested = [1, [2, 3], [[4]], 5]
all_ints = recur.to_list_of(nested) # [1, 2, 3, 4, 5]
# Through lens interface (more common)
all_ints_lens = lens.Recur(int).collect()(nested) # [1, 2, 3, 4, 5]True lenses providing read-write access to single foci.
class GetattrLens:
"""Lens for object attributes."""
def __init__(self, name: str): ...
class GetitemLens:
"""Lens for container items."""
def __init__(self, key: Any): ...
class GetitemOrElseLens:
"""Lens for container items with default values."""
def __init__(self, key: Any, default: Optional[Y] = None): ...
class ContainsLens:
"""Lens for collection containment as boolean."""
def __init__(self, item: A): ...
class ItemLens:
"""Lens for dictionary items as (key, value) pairs."""
def __init__(self, key: Any): ...
class ItemByValueLens:
"""Lens for dictionary items by value."""
def __init__(self, value: Any): ...
class PartsLens:
"""Converts fold/traversal to lens by collecting parts."""
def __init__(self, optic): ...from lenses.optics import GetitemLens, GetattrLens, ContainsLens
from lenses import lens
# Getitem lens
item_lens = GetitemLens(0)
data = [10, 20, 30]
first = item_lens.view(data) # 10
updated = item_lens.set(data, 99) # [99, 20, 30]
# Getattr lens
from collections import namedtuple
Person = namedtuple('Person', 'name age')
person = Person("Alice", 30)
name_lens = GetattrLens('name')
name = name_lens.view(person) # "Alice"
# Contains lens
contains_lens = ContainsLens(2)
data = [1, 2, 3]
has_two = contains_lens.view(data) # True
without_two = contains_lens.set(data, False) # [1, 3]
# Through lens interface (more common)
first_item = lens[0].get()(data) # 10
person_name = lens.GetAttr('name').get()(person) # "Alice"How optics compose to create complex access patterns.
def compose_optics(optic1, optic2):
"""Compose two optics into a single optic."""
return ComposedLens(optic1, optic2)from lenses.optics import GetitemLens, EachTraversal
from lenses import lens
# Manual composition (advanced usage)
first_item = GetitemLens(0)
each_item = EachTraversal()
composed = first_item.compose(each_item)
# Through lens interface (recommended)
data = [[1, 2], [3, 4], [5, 6]]
first_each = lens[0].Each().collect()(data) # [1, 2]
# Complex compositions
nested_access = (lens["users"]
.Each()
.Filter(lambda u: u.get("active", False))
["profile"]
["name"])Install with Tessl CLI
npx tessl i tessl/pypi-lenses