A lens library for python that enables immutable manipulation of deeply nested data structures
—
The fundamental lens interface providing get, set, modify operations and lens composition. These core operations form the foundation of all lens-based data manipulation in the library.
The main lens class that provides composable data access patterns without being bound to specific state. All lens constructors return UnboundLens instances.
class UnboundLens:
def __init__(self, optic): ...
def get(self) -> StateFunction[S, B]:
"""Get the first value focused by the lens."""
def collect(self) -> StateFunction[S, List[B]]:
"""Get multiple values focused by the lens as a list."""
def get_monoid(self) -> StateFunction[S, B]:
"""Get values focused by the lens, merging them as a monoid."""
def set(self, newvalue: B) -> StateFunction[S, T]:
"""Set the focus to newvalue."""
def set_many(self, new_values: Iterable[B]) -> StateFunction[S, T]:
"""Set many foci to values from new_values iterable."""
def modify(self, func: Callable[[A], B]) -> StateFunction[S, T]:
"""Apply a function to the focus."""
def construct(self, focus: A) -> S:
"""Construct a state given a focus."""
def flip(self) -> UnboundLens[A, B, S, T]:
"""Flips the direction of the lens (requires isomorphisms)."""
def kind(self) -> str:
"""Returns the 'kind' of the lens."""
def __and__(self, other) -> UnboundLens:
"""Compose this lens with another lens or apply function to focus."""
def add_lens(self, other) -> UnboundLens:
"""Alias for __and__."""from lenses import lens
# Basic getting
data = [1, 2, 3]
first = lens[0].get()(data) # 1
# Setting creates new data structures
new_data = lens[1].set(99)(data) # [1, 99, 3]
# Modifying with functions
doubled = lens.Each().modify(lambda x: x * 2)(data) # [2, 4, 6]
# Lens composition with &
nested = [[1, 2], [3, 4], [5, 6]]
first_of_each = lens.Each()[0].collect()(nested) # [1, 3, 5]
# Function application with &
add_ten = lens[0] & (lambda x: x + 10)
result = add_ten(data) # [11, 2, 3]A lens that has been bound to specific state, providing direct access without needing to pass state as a parameter.
class BoundLens:
def __init__(self, state: S, optic) -> None: ...
def get(self) -> B:
"""Get the first value focused by the lens."""
def collect(self) -> List[B]:
"""Get multiple values focused by the lens as a list."""
def get_monoid(self) -> B:
"""Get values focused by the lens, merging them as a monoid."""
def set(self, newvalue: B) -> T:
"""Set the focus to newvalue."""
def set_many(self, new_values: Iterable[B]) -> T:
"""Set many foci to values from new_values iterable."""
def modify(self, func: Callable[[A], B]) -> T:
"""Apply a function to the focus."""
def kind(self) -> str:
"""Returns the 'kind' of the lens."""
def __and__(self, other) -> BoundLens:
"""Compose this lens with another lens or apply function to focus."""
def add_lens(self, other) -> BoundLens:
"""Alias for __and__."""from lenses import bind
# Create bound lens
data = {"users": [{"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}]}
bound = bind(data)
# Direct operations without passing state
first_user_name = bound["users"][0]["name"].get() # "Alice"
updated_data = bound["users"][0]["age"].set(26)
# Composition works the same
all_ages = bound["users"].Each()["age"].collect() # [25, 30]A wrapper for functions that operate on state, returned by UnboundLens operations.
class StateFunction:
def __init__(self, func: Callable[[S], T]): ...
def __call__(self, state: S) -> T: ...from lenses import lens
# Get operations return StateFunctions
getter = lens[0].get() # StateFunction
value = getter([1, 2, 3]) # 1
# Set operations return StateFunctions
setter = lens[1].set(99) # StateFunction
new_list = setter([1, 2, 3]) # [1, 99, 3]Creates a BoundLens from state, providing an alternative entry point to the lens system.
def bind(state: S) -> BoundLens[S, S, S, S]:
"""Returns a simple BoundLens object bound to state."""from lenses import bind
# Create bound lens for immediate operations
data = [1, 2, 3, 4, 5]
bound_data = bind(data)
# Perform operations directly
first = bound_data[0].get() # 1
modified = bound_data.Each().modify(lambda x: x + 1) # [2, 3, 4, 5, 6]
# Useful for method chaining
result = (bind({"a": [1, 2, 3]})
["a"]
.Each()
.modify(lambda x: x * 2)
.collect()) # [2, 4, 6] from modified stateLenses can be composed using the & operator to create complex data access patterns.
# Composition operator
lens_a & lens_b # Compose two lenses
lens_a & function # Apply function to lens focusfrom lenses import lens
# Nested data access
data = [{"values": [1, 2, 3]}, {"values": [4, 5, 6]}]
# Compose lenses to access nested values
each_first_value = lens.Each()["values"][0]
first_values = each_first_value.collect()(data) # [1, 4]
# Function composition
multiply_by_ten = lens.Each() & (lambda x: x * 10)
result = multiply_by_ten([1, 2, 3]) # [10, 20, 30]
# Complex composition chains
complex_lens = (lens["data"]
.Each()
.Filter(lambda x: x > 0)
["value"]
.modify(lambda x: x * 2))Install with Tessl CLI
npx tessl i tessl/pypi-lenses