0
# Lens Constructors
1
2
Comprehensive collection of lens constructors for accessing different data structures and implementing various access patterns. These methods are available on both UnboundLens and BoundLens classes, providing building blocks for complex data manipulation.
3
4
## Capabilities
5
6
### Container Access
7
8
Methods for accessing elements within containers like lists, dictionaries, and objects.
9
10
```python { .api }
11
def GetItem(self, key: Any) -> BaseUiLens:
12
"""Focus an item inside a container. Analogous to operator.itemgetter."""
13
14
def GetAttr(self, name: str) -> BaseUiLens:
15
"""Focus an attribute of an object. Analogous to getattr."""
16
17
def Get(self, key: Any, default: Optional[Y] = None) -> BaseUiLens:
18
"""Focus an item with default value for missing keys. Analogous to dict.get."""
19
20
def Contains(self, item: A) -> BaseUiLens[S, S, bool, bool]:
21
"""Focus a boolean indicating whether state contains item."""
22
```
23
24
#### Usage Examples
25
26
```python
27
from lenses import lens
28
29
# GetItem (also available as [key] syntax)
30
data = [1, 2, 3]
31
lens.GetItem(0).get()(data) # 1
32
lens[0].get()(data) # Same as above
33
34
# Dictionary access
35
person = {"name": "Alice", "age": 30}
36
lens["name"].get()(person) # "Alice"
37
38
# GetAttr for object attributes
39
from collections import namedtuple
40
Person = namedtuple('Person', 'name age')
41
p = Person("Bob", 25)
42
lens.GetAttr('name').get()(p) # "Bob"
43
44
# Get with default values
45
data = {"a": 1}
46
lens.Get("b", 0).get()(data) # 0 (default)
47
lens.Get("b").set(2)(data) # {"a": 1, "b": 2}
48
49
# Contains
50
data = [1, 2, 3]
51
lens.Contains(2).get()(data) # True
52
lens.Contains(2).set(False)(data) # [1, 3] (removes item)
53
```
54
55
### Traversal and Iteration
56
57
Methods for working with multiple elements in collections.
58
59
```python { .api }
60
def Each(self) -> BaseUiLens:
61
"""Traverse all items in an iterable. Analogous to iter."""
62
63
def Items(self) -> BaseUiLens:
64
"""Traverse key-value pairs in a dictionary. Analogous to dict.items."""
65
66
def Keys(self) -> BaseUiLens:
67
"""Traverse dictionary keys. Analogous to dict.keys."""
68
69
def Values(self) -> BaseUiLens:
70
"""Traverse dictionary values. Analogous to dict.values."""
71
72
def Iter(self) -> BaseUiLens:
73
"""Read-only fold over any iterable (cannot set values)."""
74
```
75
76
#### Usage Examples
77
78
```python
79
from lenses import lens
80
81
# Each - traverse all items
82
data = [1, 2, 3, 4, 5]
83
lens.Each().collect()(data) # [1, 2, 3, 4, 5]
84
lens.Each().modify(lambda x: x * 2)(data) # [2, 4, 6, 8, 10]
85
86
# Dictionary traversals
87
data = {"a": 1, "b": 2, "c": 3}
88
lens.Items().collect()(data) # [("a", 1), ("b", 2), ("c", 3)]
89
lens.Keys().collect()(data) # ["a", "b", "c"]
90
lens.Values().collect()(data) # [1, 2, 3]
91
lens.Values().modify(lambda x: x + 10)(data) # {"a": 11, "b": 12, "c": 13}
92
93
# Each works with dictionaries by items
94
lens.Each().collect()(data) # [("a", 1), ("b", 2), ("c", 3)]
95
lens.Each()[1].modify(lambda x: x + 1)(data) # {"a": 2, "b": 3, "c": 4}
96
```
97
98
### Filtering and Selection
99
100
Methods for conditionally selecting elements based on predicates or types.
101
102
```python { .api }
103
def Filter(self, predicate: Callable[[A], bool]) -> BaseUiLens:
104
"""Focus values that satisfy the predicate."""
105
106
def Instance(self, type_: Type) -> BaseUiLens:
107
"""Focus values that are instances of the given type."""
108
109
def Just(self) -> BaseUiLens:
110
"""Focus the value inside a Just object (from Maybe type)."""
111
```
112
113
#### Usage Examples
114
115
```python
116
from lenses import lens
117
118
# Filter by predicate
119
data = [1, -2, 3, -4, 5]
120
positive = lens.Each().Filter(lambda x: x > 0)
121
positive.collect()(data) # [1, 3, 5]
122
positive.modify(lambda x: x * 10)(data) # [10, -2, 30, -4, 50]
123
124
# Instance type filtering
125
mixed_data = [1, "hello", 2.5, "world", 42]
126
strings = lens.Each().Instance(str)
127
strings.collect()(mixed_data) # ["hello", "world"]
128
129
# Just for Maybe types
130
from lenses.maybe import Just, Nothing
131
data = [Just(1), Nothing(), Just(3)]
132
values = lens.Each().Just()
133
values.collect()(data) # [1, 3]
134
```
135
136
### Transformations and Isomorphisms
137
138
Methods for converting between different data representations.
139
140
```python { .api }
141
def Json(self) -> BaseUiLens:
142
"""Focus string as parsed JSON data. Analogous to json.loads."""
143
144
def Decode(self, encoding: str = "utf-8", errors: str = "strict") -> BaseUiLens:
145
"""Focus bytes as decoded string. Analogous to bytes.decode."""
146
147
def Iso(self, forwards: Callable[[A], X], backwards: Callable[[Y], B]) -> BaseUiLens:
148
"""Create an isomorphism from forward and backward functions."""
149
150
def Norm(self, setter: Callable[[A], X]) -> BaseUiLens:
151
"""Apply normalization function when setting values."""
152
```
153
154
#### Usage Examples
155
156
```python
157
from lenses import lens
158
159
# JSON parsing
160
json_data = '{"users": [{"name": "Alice"}, {"name": "Bob"}]}'
161
users = lens.Json()["users"].Each()["name"].collect()(json_data) # ["Alice", "Bob"]
162
163
# Update JSON
164
updated = lens.Json()["users"][0]["name"].set("Charlie")(json_data)
165
# '{"users": [{"name": "Charlie"}, {"name": "Bob"}]}'
166
167
# Decode bytes
168
byte_data = b"hello world"
169
text = lens.Decode().get()(byte_data) # "hello world"
170
171
# Custom isomorphisms
172
chr_ord_iso = lens.Iso(chr, ord) # char <-> int conversion
173
lens.Iso(chr, ord).get()(65) # 'A'
174
lens.Iso(chr, ord).set('B')(65) # 66
175
176
# Normalization
177
normalize_int = lens[0].Norm(int)
178
normalize_int.set(4.7)([1, 2, 3]) # [4, 2, 3] (4.7 -> 4)
179
```
180
181
### Advanced Access Patterns
182
183
Specialized lens constructors for complex access patterns.
184
185
```python { .api }
186
def Item(self, key: Any) -> BaseUiLens:
187
"""Focus a dictionary item as (key, value) pair."""
188
189
def ItemByValue(self, value: Any) -> BaseUiLens:
190
"""Focus dictionary item by its value."""
191
192
def Recur(self, cls) -> BaseUiLens:
193
"""Recursively focus all objects of given type."""
194
195
def Regex(self, pattern: Union[str, Pattern], flags: int = 0) -> BaseUiLens:
196
"""Focus parts of string matching regex pattern."""
197
198
def Parts(self) -> BaseUiLens:
199
"""Convert fold/traversal into lens by focusing list of all parts."""
200
```
201
202
#### Usage Examples
203
204
```python
205
from lenses import lens
206
207
# Item as key-value pairs
208
data = {"a": 1, "b": 2}
209
lens.Item("a").get()(data) # ("a", 1)
210
lens.Item("a").set(("a", 10))(data) # {"a": 10, "b": 2}
211
lens.Item("a").set(None)(data) # {"b": 2} (removes item)
212
213
# ItemByValue
214
data = {"x": 10, "y": 20, "z": 10}
215
lens.ItemByValue(10).get()(data) # ("x", 10) (first match)
216
217
# Recursive type traversal
218
data = [1, [2, [3, 4], 5], 6]
219
lens.Recur(int).collect()(data) # [1, 2, 3, 4, 5, 6]
220
221
# Regex patterns
222
text = "The quick brown fox"
223
words = lens.Regex(r'\w+')
224
words.collect()(text) # ["The", "quick", "brown", "fox"]
225
words.modify(lambda w: w.upper())(text) # "THE QUICK BROWN FOX"
226
227
# Parts - convert traversals to lenses
228
nested = [[1, 2], [3, 4], [5, 6]]
229
all_nums = lens.Each().Each().Parts()
230
all_nums.get()(nested) # [1, 2, 3, 4, 5, 6]
231
all_nums[0].set(99)(nested) # [[99, 2], [3, 4], [5, 6]]
232
```
233
234
### Custom Lens Creation
235
236
Methods for creating custom lenses from functions.
237
238
```python { .api }
239
def Lens(self, getter: Callable[[A], X], setter: Callable[[A, Y], B]) -> BaseUiLens:
240
"""Create custom lens from getter and setter functions."""
241
242
def F(self, getter: Callable[[A], X]) -> BaseUiLens:
243
"""Create getter-only lens from function."""
244
245
def Prism(self, unpack: Callable[[A], Just[X]], pack: Callable[[Y], B],
246
ignore_none: bool = False, ignore_errors: Optional[tuple] = None) -> BaseUiLens:
247
"""Create prism from unpack/pack functions with optional error handling."""
248
249
def Traversal(self, folder: Callable[[A], Iterable[X]],
250
builder: Callable[[A, Iterable[Y]], B]) -> BaseUiLens:
251
"""Create custom traversal from folder and builder functions."""
252
253
def Fold(self, func: Callable[[A], Iterable[X]]) -> BaseUiLens:
254
"""Create read-only fold from function returning iterable."""
255
```
256
257
#### Usage Examples
258
259
```python
260
from lenses import lens
261
262
# Custom lens for list average
263
def get_avg(lst):
264
return sum(lst) / len(lst)
265
266
def set_avg(old_lst, new_avg):
267
# Set average by adjusting last element
268
target_sum = new_avg * len(old_lst)
269
return old_lst[:-1] + [target_sum - sum(old_lst[:-1])]
270
271
avg_lens = lens.Lens(get_avg, set_avg)
272
avg_lens.get()([1, 2, 3]) # 2.0
273
avg_lens.set(5)([1, 2, 3]) # [1, 2, 12] (avg is now 5)
274
275
# Getter-only lens
276
abs_lens = lens.Each().F(abs)
277
abs_lens.collect()([-1, 2, -3]) # [1, 2, 3]
278
279
# Custom traversal for list endpoints
280
def ends_folder(lst):
281
yield lst[0]
282
yield lst[-1]
283
284
def ends_builder(old_lst, new_values):
285
new_vals = list(new_values)
286
result = list(old_lst)
287
result[0] = new_vals[0]
288
result[-1] = new_vals[1]
289
return result
290
291
ends_lens = lens.Traversal(ends_folder, ends_builder)
292
ends_lens.collect()([1, 2, 3, 4]) # [1, 4]
293
ends_lens.set(99)([1, 2, 3, 4]) # [99, 2, 3, 99]
294
```
295
296
### Composition and Parallel Operations
297
298
Methods for combining multiple lenses or operations.
299
300
```python { .api }
301
def Fork(self, *lenses: BaseUiLens) -> BaseUiLens:
302
"""Parallel composition of multiple sub-lenses."""
303
304
def Tuple(self, *lenses: BaseUiLens) -> BaseUiLens:
305
"""Combine focuses of multiple lenses into a tuple."""
306
```
307
308
#### Usage Examples
309
310
```python
311
from lenses import lens
312
313
# Fork - parallel operations
314
data = [1, 2, 3, 4, 5]
315
fork_lens = lens.Fork(lens[0], lens[2], lens[4])
316
fork_lens.set(99)(data) # [99, 2, 99, 4, 99]
317
318
# Tuple - combine multiple focuses
319
data = [10, 20, 30, 40]
320
tuple_lens = lens.Tuple(lens[0], lens[3])
321
tuple_lens.get()(data) # (10, 40)
322
tuple_lens.set((1, 9))(data) # [1, 20, 30, 9]
323
324
# Useful with Each for cross-product operations
325
state = ([1, 2], [3, 4])
326
cross = lens.Tuple(lens[0], lens[1]).Each().Each()
327
cross.collect()(state) # [1, 2, 3, 4]
328
```
329
330
### Special and Debugging Lenses
331
332
Utility lenses for debugging and special cases.
333
334
```python { .api }
335
def Error(self, exception: Exception, message: Optional[str] = None) -> BaseUiLens:
336
"""Lens that raises exception when focused (for debugging)."""
337
338
def GetZoomAttr(self, name: str) -> BaseUiLens:
339
"""Focus attribute, zooming if it's a lens itself."""
340
341
def Zoom(self) -> BaseUiLens:
342
"""Follow state as if it were a BoundLens object."""
343
344
def ZoomAttr(self, name: str) -> BaseUiLens:
345
"""Focus attribute and follow it as BoundLens."""
346
```
347
348
#### Usage Examples
349
350
```python
351
from lenses import lens, bind
352
353
# Error lens for debugging
354
debug_lens = lens.Error(ValueError("Debug breakpoint"))
355
# debug_lens.get()(data) # Raises ValueError
356
357
# Zoom lenses work with bound lenses as data
358
data = [bind([1, 2, 3])[1], 4, 5]
359
lens[0].Zoom().get()(data) # 2 (follows the bound lens)
360
lens[0].Zoom().set(99)(data) # [[1, 99, 3], 4, 5]
361
```