Skip to content

Optics (Lenses)

funstruct.experimental.optics

Optics — composable getters and setters for immutable data.

Lenses let you read and update deeply nested immutable structures without manually rebuilding the path at every level.

Examples:

>>> from funstruct.experimental.optics import Lens, at
>>> from funstruct.collections.frozendict import frozendict
>>> users = frozendict({
...     "alice": {"profile": {"age": 30, "city": "NYC"}},
... })
>>> age_lens = at("alice") >> at("profile") >> at("age")
>>> age_lens.get(users)
30
>>> age_lens.set(users, 31)["alice"]["profile"]["age"]
31
>>> age_lens.modify(users, lambda x: x + 1)["alice"]["profile"]["age"]
31

Status: experimental. API may change.

Lens dataclass

A composable getter/setter pair.

from funstruct.collections.frozendict import frozendict fd = frozendict({"a": frozendict({"b": 1})}) lens = at("a") >> at("b") lens.get(fd) 1 lens.set(fd, 99)["a"]["b"] 99 lens.modify(fd, lambda x: x + 1)["a"]["b"] 2

Source code in funstruct/experimental/optics/_lens.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@dataclass(frozen=True)
class Lens:
    """A composable getter/setter pair.

    >>> from funstruct.collections.frozendict import frozendict
    >>> fd = frozendict({"a": frozendict({"b": 1})})
    >>> lens = at("a") >> at("b")
    >>> lens.get(fd)
    1
    >>> lens.set(fd, 99)["a"]["b"]
    99
    >>> lens.modify(fd, lambda x: x + 1)["a"]["b"]
    2
    """

    _get: Callable
    _set: Callable

    def get(self, s):
        return self._get(s)

    def set(self, s, value):
        return self._set(s, value)

    def modify(self, s, f: Callable):
        return self.set(s, f(self.get(s)))

    def __rshift__(self, other: Lens) -> Lens:
        return _compose(self, other)

at(key)

Create a lens that focuses on a key in a dict-like structure.

Works with frozendict (uses put) and plain dicts (uses spread).

from funstruct.collections.frozendict import frozendict lens = at("x") lens.get(frozendict({"x": 42})) 42 lens.set(frozendict({"x": 42}), 99)["x"] 99

Source code in funstruct/experimental/optics/_lens.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def at(key) -> Lens:
    """Create a lens that focuses on a key in a dict-like structure.

    Works with frozendict (uses put) and plain dicts (uses spread).

    >>> from funstruct.collections.frozendict import frozendict
    >>> lens = at("x")
    >>> lens.get(frozendict({"x": 42}))
    42
    >>> lens.set(frozendict({"x": 42}), 99)["x"]
    99
    """
    from funstruct.collections.frozendict import frozendict

    def _set(s, value):
        match s:
            case frozendict():
                return s.put(key, value)
            case dict():
                return {**s, key: value}
            case _:
                raise TypeError(f"at({key!r}): cannot set on {type(s).__name__}")

    return Lens(
        _get=lambda s: s[key],
        _set=_set,
    )