dtools.fp 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dtools/fp/__init__.py +34 -0
- dtools/fp/err_handling.py +436 -0
- dtools/fp/function.py +71 -0
- dtools/fp/iterables.py +413 -0
- dtools/fp/lazy.py +126 -0
- dtools/fp/py.typed +0 -0
- dtools/fp/singletons.py +238 -0
- dtools/fp/state.py +107 -0
- dtools_fp-1.3.0.dist-info/LICENSE +176 -0
- dtools_fp-1.3.0.dist-info/METADATA +58 -0
- dtools_fp-1.3.0.dist-info/RECORD +12 -0
- dtools_fp-1.3.0.dist-info/WHEEL +4 -0
dtools/fp/iterables.py
ADDED
@@ -0,0 +1,413 @@
|
|
1
|
+
# Copyright 2023-2025 Geoffrey R. Scheller
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
"""### Module fp.iterables - Iterator related tools
|
16
|
+
|
17
|
+
Library of iterator related functions and enumerations.
|
18
|
+
|
19
|
+
* iterables are not necessarily iterators
|
20
|
+
* at all times iterator protocol is assumed to be followed, that is
|
21
|
+
* all iterators are assumed to be iterable
|
22
|
+
* for all iterators `foo` we assume `iter(foo) is foo`
|
23
|
+
|
24
|
+
#### Concatenating and merging iterables:
|
25
|
+
|
26
|
+
* function **concat**: sequentially chain iterables
|
27
|
+
* function **exhaust**: shuffle together iterables until all are exhausted
|
28
|
+
* function **merge**: shuffle together iterables until one is exhausted
|
29
|
+
|
30
|
+
---
|
31
|
+
|
32
|
+
#### Dropping and taking values from an iterable:
|
33
|
+
|
34
|
+
* function **drop**: drop first `n` values from iterable
|
35
|
+
* function **drop_while**: drop values from iterable while predicate holds
|
36
|
+
* function **take**: take up to `n` initial values from iterable
|
37
|
+
* function **take_split**: splitting out initial `n` initial values of iterable * function **take_while**: take values from iterable while predicate holds
|
38
|
+
* function **take_while_split**: splitting an iterable while predicate holds
|
39
|
+
|
40
|
+
---
|
41
|
+
|
42
|
+
#### Reducing and accumulating an iterable:
|
43
|
+
|
44
|
+
* function **accumulate**: take iterable & function, return iterator of accumulated values
|
45
|
+
* function **foldL0**: fold iterable left with a function
|
46
|
+
* raises `StopIteration` exception if iterable is empty
|
47
|
+
* function **foldL1**: fold iterable left with a function and initial value
|
48
|
+
* function **mbFoldL**: fold iterable left with an optional initial value
|
49
|
+
* wraps result in a `MB` monad
|
50
|
+
|
51
|
+
"""
|
52
|
+
from __future__ import annotations
|
53
|
+
from collections.abc import Callable, Iterator, Iterable, Reversible
|
54
|
+
from enum import auto, Enum
|
55
|
+
from typing import cast, Never, Protocol
|
56
|
+
from .err_handling import MB
|
57
|
+
from .function import swap
|
58
|
+
from .singletons import NoValue
|
59
|
+
|
60
|
+
__all__ = [ 'FM', 'concat', 'merge', 'exhaust',
|
61
|
+
'drop', 'drop_while',
|
62
|
+
'take', 'take_while',
|
63
|
+
'take_split', 'take_while_split',
|
64
|
+
'accumulate', 'foldL0', 'foldL1', 'mbFoldL' ] #,
|
65
|
+
# 'scFoldL', 'scFoldR' ]
|
66
|
+
|
67
|
+
## Iterate over multiple Iterables
|
68
|
+
|
69
|
+
class FM(Enum):
|
70
|
+
CONCAT = auto()
|
71
|
+
MERGE = auto()
|
72
|
+
EXHAUST = auto()
|
73
|
+
|
74
|
+
def concat[D](*iterables: Iterable[D]) -> Iterator[D]:
|
75
|
+
"""Sequentially concatenate multiple iterables together.
|
76
|
+
|
77
|
+
* pure Python version of standard library's `itertools.chain`
|
78
|
+
* iterator sequentially yields each iterable until all are exhausted
|
79
|
+
* an infinite iterable will prevent subsequent iterables from yielding any values
|
80
|
+
* performant to `itertools.chain`
|
81
|
+
|
82
|
+
"""
|
83
|
+
for iterator in map(lambda x: iter(x), iterables):
|
84
|
+
while True:
|
85
|
+
try:
|
86
|
+
value = next(iterator)
|
87
|
+
yield value
|
88
|
+
except StopIteration:
|
89
|
+
break
|
90
|
+
|
91
|
+
def exhaust[D](*iterables: Iterable[D]) -> Iterator[D]:
|
92
|
+
"""Shuffle together multiple iterables until all are exhausted.
|
93
|
+
|
94
|
+
* iterator yields until all iterables are exhausted
|
95
|
+
|
96
|
+
"""
|
97
|
+
iterList = list(map(lambda x: iter(x), iterables))
|
98
|
+
if (numIters := len(iterList)) > 0:
|
99
|
+
ii = 0
|
100
|
+
values = []
|
101
|
+
while True:
|
102
|
+
try:
|
103
|
+
while ii < numIters:
|
104
|
+
values.append(next(iterList[ii]))
|
105
|
+
ii += 1
|
106
|
+
for value in values:
|
107
|
+
yield value
|
108
|
+
ii = 0
|
109
|
+
values.clear()
|
110
|
+
except StopIteration:
|
111
|
+
numIters -= 1
|
112
|
+
if numIters < 1:
|
113
|
+
break
|
114
|
+
del iterList[ii]
|
115
|
+
for value in values:
|
116
|
+
yield value
|
117
|
+
|
118
|
+
def merge[D](*iterables: Iterable[D], yield_partials: bool=False) -> Iterator[D]:
|
119
|
+
"""Shuffle together the `iterables` until one is exhausted.
|
120
|
+
|
121
|
+
* iterator yields until one of the iterables is exhausted
|
122
|
+
* if `yield_partials` is true,
|
123
|
+
* yield any unmatched yielded values from other iterables
|
124
|
+
* prevents data lose
|
125
|
+
* if any of the iterables are iterators with external references
|
126
|
+
|
127
|
+
"""
|
128
|
+
iterList = list(map(lambda x: iter(x), iterables))
|
129
|
+
values = []
|
130
|
+
if (numIters := len(iterList)) > 0:
|
131
|
+
while True:
|
132
|
+
try:
|
133
|
+
for ii in range(numIters):
|
134
|
+
values.append(next(iterList[ii]))
|
135
|
+
for value in values:
|
136
|
+
yield value
|
137
|
+
values.clear()
|
138
|
+
except StopIteration:
|
139
|
+
break
|
140
|
+
if yield_partials:
|
141
|
+
for value in values:
|
142
|
+
yield value
|
143
|
+
|
144
|
+
## dropping and taking
|
145
|
+
|
146
|
+
def drop[D](
|
147
|
+
iterable: Iterable[D],
|
148
|
+
n: int, /
|
149
|
+
) -> Iterator[D]:
|
150
|
+
"""Drop the next `n` values from `iterable`."""
|
151
|
+
it = iter(iterable)
|
152
|
+
for _ in range(n):
|
153
|
+
try:
|
154
|
+
next(it)
|
155
|
+
except StopIteration:
|
156
|
+
break
|
157
|
+
return it
|
158
|
+
|
159
|
+
def drop_while[D](
|
160
|
+
iterable: Iterable[D],
|
161
|
+
predicate: Callable[[D], bool], /
|
162
|
+
) -> Iterator[D]:
|
163
|
+
"""Drop initial values from `iterable` while predicate is true."""
|
164
|
+
it = iter(iterable)
|
165
|
+
while True:
|
166
|
+
try:
|
167
|
+
value = next(it)
|
168
|
+
if not predicate(value):
|
169
|
+
it = concat((value,), it)
|
170
|
+
break
|
171
|
+
except StopIteration:
|
172
|
+
break
|
173
|
+
return it
|
174
|
+
|
175
|
+
def take[D](
|
176
|
+
iterable: Iterable[D],
|
177
|
+
n: int, /
|
178
|
+
) -> Iterator[D]:
|
179
|
+
"""Return an iterator of up to `n` initial values of an iterable"""
|
180
|
+
it = iter(iterable)
|
181
|
+
for _ in range(n):
|
182
|
+
try:
|
183
|
+
value = next(it)
|
184
|
+
yield value
|
185
|
+
except StopIteration:
|
186
|
+
break
|
187
|
+
|
188
|
+
def take_split[D](
|
189
|
+
iterable: Iterable[D],
|
190
|
+
n: int, /
|
191
|
+
) -> tuple[Iterator[D], Iterator[D]]:
|
192
|
+
"""Same as take except also return an iterator of the remaining values.
|
193
|
+
|
194
|
+
* return a tuple of
|
195
|
+
* an iterator of up to `n` initial values
|
196
|
+
* an iterator of the remaining vales of the `iterable`
|
197
|
+
* best practice is not to access second iterator until first is exhausted
|
198
|
+
|
199
|
+
"""
|
200
|
+
it = iter(iterable)
|
201
|
+
itn = take(it, n)
|
202
|
+
|
203
|
+
return itn, it
|
204
|
+
|
205
|
+
def take_while[D](
|
206
|
+
iterable: Iterable[D],
|
207
|
+
pred: Callable[[D], bool], /
|
208
|
+
) -> Iterator[D]:
|
209
|
+
"""Yield values from `iterable` while predicate is true.
|
210
|
+
|
211
|
+
**Warning:** risk of potential value loss if iterable is iterator with
|
212
|
+
multiple references.
|
213
|
+
"""
|
214
|
+
it = iter(iterable)
|
215
|
+
while True:
|
216
|
+
try:
|
217
|
+
value = next(it)
|
218
|
+
if pred(value):
|
219
|
+
yield value
|
220
|
+
else:
|
221
|
+
break
|
222
|
+
except StopIteration:
|
223
|
+
break
|
224
|
+
|
225
|
+
def take_while_split[D](
|
226
|
+
iterable: Iterable[D],
|
227
|
+
predicate: Callable[[D], bool], /
|
228
|
+
) -> tuple[Iterator[D], Iterator[D]]:
|
229
|
+
"""Yield values from `iterable` while `predicate` is true.
|
230
|
+
|
231
|
+
* return a tuple of two iterators
|
232
|
+
* first of initial values where predicate is true, followed by first to fail
|
233
|
+
* second of the remaining values of the iterable after first failed value
|
234
|
+
* best practice is not to access second iterator until first is exhausted
|
235
|
+
|
236
|
+
"""
|
237
|
+
def _take_while(it: Iterator[D], pred: Callable[[D], bool], val: list[D]) -> Iterator[D]:
|
238
|
+
while True:
|
239
|
+
try:
|
240
|
+
if val:
|
241
|
+
val[0] = next(it)
|
242
|
+
else:
|
243
|
+
val.append(next(it))
|
244
|
+
if pred(val[0]):
|
245
|
+
yield val[0]
|
246
|
+
val.pop()
|
247
|
+
else:
|
248
|
+
break
|
249
|
+
except StopIteration:
|
250
|
+
break
|
251
|
+
|
252
|
+
it = iter(iterable)
|
253
|
+
value: list[D] = []
|
254
|
+
it_pred = _take_while(it, predicate, value)
|
255
|
+
|
256
|
+
return (it_pred, concat(value, it))
|
257
|
+
|
258
|
+
## reducing and accumulating
|
259
|
+
|
260
|
+
def accumulate[D,L](
|
261
|
+
iterable: Iterable[D],
|
262
|
+
f: Callable[[L, D], L],
|
263
|
+
initial: L|NoValue=NoValue(), /
|
264
|
+
) -> Iterator[L]:
|
265
|
+
"""Returns an iterator of accumulated values.
|
266
|
+
|
267
|
+
* pure Python version of standard library's `itertools.accumulate`
|
268
|
+
* function `f` does not default to addition (for typing flexibility)
|
269
|
+
* begins accumulation with an optional `initial` value
|
270
|
+
|
271
|
+
"""
|
272
|
+
it = iter(iterable)
|
273
|
+
try:
|
274
|
+
it0 = next(it)
|
275
|
+
except StopIteration:
|
276
|
+
if initial is NoValue():
|
277
|
+
return
|
278
|
+
else:
|
279
|
+
yield cast(L, initial)
|
280
|
+
else:
|
281
|
+
if initial is not NoValue():
|
282
|
+
init = cast(L, initial)
|
283
|
+
yield init
|
284
|
+
acc = f(init, it0)
|
285
|
+
for ii in it:
|
286
|
+
yield acc
|
287
|
+
acc = f(acc, ii)
|
288
|
+
yield acc
|
289
|
+
else:
|
290
|
+
acc = cast(L, it0) # in this case L = D
|
291
|
+
for ii in it:
|
292
|
+
yield acc
|
293
|
+
acc = f(acc, ii)
|
294
|
+
yield acc
|
295
|
+
|
296
|
+
def foldL0[D](
|
297
|
+
iterable: Iterable[D],
|
298
|
+
f: Callable[[D, D], D], /
|
299
|
+
) -> D|Never:
|
300
|
+
"""Folds an iterable left with optional initial value.
|
301
|
+
|
302
|
+
* traditional FP type order given for function `f`
|
303
|
+
* if iterable empty raises StopIteration exception
|
304
|
+
* does not catch any exception `f` raises
|
305
|
+
* never returns if `iterable` generates an infinite iterator
|
306
|
+
|
307
|
+
"""
|
308
|
+
it = iter(iterable)
|
309
|
+
try:
|
310
|
+
acc = next(it)
|
311
|
+
except StopIteration:
|
312
|
+
msg = "Attemped to left fold an empty iterable."
|
313
|
+
raise StopIteration(msg)
|
314
|
+
|
315
|
+
for v in it:
|
316
|
+
acc = f(acc, v)
|
317
|
+
|
318
|
+
return acc
|
319
|
+
|
320
|
+
def foldL1[D, L](
|
321
|
+
iterable: Iterable[D],
|
322
|
+
f: Callable[[L, D], L],
|
323
|
+
initial: L, /
|
324
|
+
) -> L|Never:
|
325
|
+
"""Folds an iterable left with optional initial value.
|
326
|
+
|
327
|
+
* traditional FP type order given for function `f`
|
328
|
+
* does not catch any exception `f` may raise
|
329
|
+
* like builtin `sum` for Python >=3.8 except
|
330
|
+
- not restricted to __add__ for the folding function
|
331
|
+
- initial value required, does not default to `0` for initial value
|
332
|
+
- handles non-numeric data just find
|
333
|
+
* never returns if `iterable` generates an infinite iterator
|
334
|
+
|
335
|
+
"""
|
336
|
+
acc = initial
|
337
|
+
for v in iterable:
|
338
|
+
acc = f(acc, v)
|
339
|
+
return acc
|
340
|
+
|
341
|
+
def mbFoldL[L, D](
|
342
|
+
iterable: Iterable[D],
|
343
|
+
f: Callable[[L, D], L],
|
344
|
+
initial: L|NoValue=NoValue()
|
345
|
+
) -> MB[L]:
|
346
|
+
"""Folds an iterable left with optional initial value.
|
347
|
+
|
348
|
+
* traditional FP type order given for function `f`
|
349
|
+
* when an initial value is not given then `~L = ~D`
|
350
|
+
* if iterable empty and no `initial` value given, return `MB()`
|
351
|
+
* never returns if iterable generates an infinite iterator
|
352
|
+
|
353
|
+
"""
|
354
|
+
acc: L
|
355
|
+
it = iter(iterable)
|
356
|
+
if initial is NoValue():
|
357
|
+
try:
|
358
|
+
acc = cast(L, next(it)) # in this case L = D
|
359
|
+
except StopIteration:
|
360
|
+
return MB()
|
361
|
+
else:
|
362
|
+
acc = cast(L, initial)
|
363
|
+
|
364
|
+
for v in it:
|
365
|
+
try:
|
366
|
+
acc = f(acc, v)
|
367
|
+
except Exception:
|
368
|
+
return MB()
|
369
|
+
|
370
|
+
return MB(acc)
|
371
|
+
|
372
|
+
|
373
|
+
#def scFoldL[D, L](iterable: Iterable[D],
|
374
|
+
# f: Callable[[L, D], L],
|
375
|
+
# initial: L|NoValue=NoValue(), /,
|
376
|
+
# start_folding: Callable[[D], bool]=lambda d: True,
|
377
|
+
# stop_folding: Callable[[D], bool]=lambda d: False,
|
378
|
+
# include_start: bool=True,
|
379
|
+
# propagate_failed: bool=True) -> tuple[MB[L], Iterable[D]]:
|
380
|
+
# """Short circuit version of a left fold. Useful for infinite or
|
381
|
+
# non-reversible iterables.
|
382
|
+
#
|
383
|
+
# * Behavior for default arguments will
|
384
|
+
# * left fold finite iterable
|
385
|
+
# * start folding immediately
|
386
|
+
# * continue folding until end (of a possibly infinite iterable)
|
387
|
+
# * Callable `start_folding` delays starting a left fold
|
388
|
+
# * Callable `stop_folding` is to prematurely stop the folding left
|
389
|
+
# * Returns an XOR of either the folded value or error string
|
390
|
+
#
|
391
|
+
# """
|
392
|
+
#
|
393
|
+
#def scFoldR[D, R](iterable: Iterable[D],
|
394
|
+
# f: Callable[[D, R], R],
|
395
|
+
# initial: R|NoValue=NoValue(), /,
|
396
|
+
# start_folding: Callable[[D], bool]=lambda d: False,
|
397
|
+
# stop_folding: Callable[[D], bool]=lambda d: False,
|
398
|
+
# include_start: bool=True,
|
399
|
+
# include_stop: bool=True) -> tuple[MB[R], Iterable[D]]:
|
400
|
+
# """Short circuit version of a right fold. Useful for infinite or
|
401
|
+
# non-reversible iterables.
|
402
|
+
#
|
403
|
+
# * Behavior for default arguments will
|
404
|
+
# * right fold finite iterable
|
405
|
+
# * start folding at end (of a possibly infinite iterable)
|
406
|
+
# * continue folding right until beginning
|
407
|
+
# * Callable `start_folding` prematurely starts a right fold
|
408
|
+
# * Callable `stop_folding` is to prematurely stops a right fold
|
409
|
+
# * Returns an XOR of either the folded value or error string
|
410
|
+
# * best practice is not to access second iterator until first is exhausted
|
411
|
+
#
|
412
|
+
# """
|
413
|
+
|
dtools/fp/lazy.py
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
# Copyright 2023-2025 Geoffrey R. Scheller
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
"""### Module fp.lazy - lazy function evaluations
|
16
|
+
|
17
|
+
Delayed function evaluations, if needed, usually in some inner scope. FP tools
|
18
|
+
for "non-strict" function evaluations.
|
19
|
+
|
20
|
+
#### Non-strict delayed function evaluation:
|
21
|
+
|
22
|
+
* class **Lazy:** Delay evaluation of function taking & returning single values
|
23
|
+
* function **lazy:** Delay evaluation of a function taking more than one value
|
24
|
+
|
25
|
+
"""
|
26
|
+
from __future__ import annotations
|
27
|
+
|
28
|
+
__all__ = [ 'Lazy', 'lazy' ]
|
29
|
+
|
30
|
+
from collections.abc import Callable
|
31
|
+
from typing import Final
|
32
|
+
from .err_handling import MB, XOR
|
33
|
+
from .function import sequenced
|
34
|
+
|
35
|
+
class Lazy[D, R]():
|
36
|
+
"""Delayed evaluation of a function mapping a value of type D
|
37
|
+
|
38
|
+
Class instance delays the executable of a function where `Lazy(f, arg)`
|
39
|
+
constructs an object that can evaluate the Callable `f` with its argument
|
40
|
+
at a later time.
|
41
|
+
|
42
|
+
* first argument `f` taking values of type `~D` to values of type `~R`
|
43
|
+
* second argument `arg: ~D` is the argument to be passed to `f`
|
44
|
+
* where the type `~D` is the `tuple` type of the argument types to `f`
|
45
|
+
* function is evaluated when the eval method is called
|
46
|
+
* result is cached unless `pure` is set to `False` in `__init__` method
|
47
|
+
|
48
|
+
Usually use case is to make a function "non-strict" by passing some of its
|
49
|
+
arguments wrapped in Lazy instances.
|
50
|
+
"""
|
51
|
+
__slots__ = '_f', '_d', '_result', '_pure'
|
52
|
+
|
53
|
+
def __init__(self, f: Callable[[D], R], d: D, pure: bool=True) -> None:
|
54
|
+
self._f: Final[Callable[[D], R]] = f
|
55
|
+
self._d: Final[D] = d
|
56
|
+
self._pure: Final[bool] = pure
|
57
|
+
self._result: XOR[R, MB[Exception]] = XOR(MB(), MB())
|
58
|
+
|
59
|
+
def __bool__(self) -> bool:
|
60
|
+
return True if self._result else False
|
61
|
+
|
62
|
+
def is_evaluated(self) -> bool:
|
63
|
+
return self._result != XOR(MB(), MB())
|
64
|
+
|
65
|
+
def is_exceptional(self) -> bool:
|
66
|
+
if self.is_evaluated():
|
67
|
+
return False if self._result else True
|
68
|
+
else:
|
69
|
+
return False
|
70
|
+
|
71
|
+
def is_pure(self) -> bool:
|
72
|
+
return self._pure
|
73
|
+
|
74
|
+
def eval(self) -> bool:
|
75
|
+
"""Evaluate function with its argument.
|
76
|
+
|
77
|
+
* evaluate function
|
78
|
+
* cache results or exceptions if `pure == True`
|
79
|
+
* reevaluate if `pure == False`
|
80
|
+
|
81
|
+
"""
|
82
|
+
if not self.is_evaluated() or not self._pure:
|
83
|
+
try:
|
84
|
+
result = self._f(self._d)
|
85
|
+
except Exception as exc:
|
86
|
+
self._result = XOR(MB(), MB(exc))
|
87
|
+
return False
|
88
|
+
else:
|
89
|
+
self._result = XOR(MB(result), MB())
|
90
|
+
return True
|
91
|
+
if self:
|
92
|
+
return True
|
93
|
+
else:
|
94
|
+
return False
|
95
|
+
|
96
|
+
def result(self) -> MB[R]:
|
97
|
+
if not self.is_evaluated():
|
98
|
+
self.eval()
|
99
|
+
|
100
|
+
if self._result:
|
101
|
+
return MB(self._result.getLeft())
|
102
|
+
else:
|
103
|
+
return MB()
|
104
|
+
|
105
|
+
def exception(self) -> MB[Exception]:
|
106
|
+
if not self.is_evaluated():
|
107
|
+
self.eval()
|
108
|
+
return self._result.getRight()
|
109
|
+
|
110
|
+
def lazy[R, **P](f: Callable[P, R], *args: P.args, pure: bool=True) -> Lazy[tuple[P.args], R]:
|
111
|
+
"""Delayed evaluation of a function with arbitrary positional arguments.
|
112
|
+
|
113
|
+
Function returning a delayed evaluation of a function of an arbitrary number
|
114
|
+
of positional arguments.
|
115
|
+
|
116
|
+
* first positional argument `f` takes a function
|
117
|
+
* next positional arguments are the arguments to be applied later to `f`
|
118
|
+
* `f` is evaluated when the `eval` method of the returned Lazy is called
|
119
|
+
* `f` is evaluated only once with results cached unless `pure` is `False`
|
120
|
+
* if `pure` is false, the arguments are reapplied to `f`
|
121
|
+
* useful for repeating side effects
|
122
|
+
* when arguments are or contain shared references
|
123
|
+
|
124
|
+
"""
|
125
|
+
return Lazy(sequenced(f), args, pure=pure)
|
126
|
+
|
dtools/fp/py.typed
ADDED
File without changes
|