grscheller.fp.lazy
Module fp.lazy - lazy function evaluations
Delayed function evaluations, if needed, usually in some inner scope. FP tools for "non-strict" function evaluations.
Non-strict delayed function evaluation:
- class Lazy: Delay evaluation of function taking & returning single values
- function lazy: Delay evaluation of a function taking more than one value
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 17Delayed function evaluations, if needed, usually in some inner scope. FP tools 18for "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""" 26from __future__ import annotations 27 28__all__ = [ 'Lazy', 'lazy' ] 29 30from collections.abc import Callable 31from typing import Final 32from .err_handling import MB, XOR 33from .function import sequenced 34 35class 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 110def 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)
class
Lazy(typing.Generic[D, R]):
36class Lazy[D, R](): 37 """Delayed evaluation of a function mapping a value of type D 38 39 Class instance delays the executable of a function where `Lazy(f, arg)` 40 constructs an object that can evaluate the Callable `f` with its argument 41 at a later time. 42 43 * first argument `f` taking values of type `~D` to values of type `~R` 44 * second argument `arg: ~D` is the argument to be passed to `f` 45 * where the type `~D` is the `tuple` type of the argument types to `f` 46 * function is evaluated when the eval method is called 47 * result is cached unless `pure` is set to `False` in `__init__` method 48 49 Usually use case is to make a function "non-strict" by passing some of its 50 arguments wrapped in Lazy instances. 51 """ 52 __slots__ = '_f', '_d', '_result', '_pure' 53 54 def __init__(self, f: Callable[[D], R], d: D, pure: bool=True) -> None: 55 self._f: Final[Callable[[D], R]] = f 56 self._d: Final[D] = d 57 self._pure: Final[bool] = pure 58 self._result: XOR[R, MB[Exception]] = XOR(MB(), MB()) 59 60 def __bool__(self) -> bool: 61 return True if self._result else False 62 63 def is_evaluated(self) -> bool: 64 return self._result != XOR(MB(), MB()) 65 66 def is_exceptional(self) -> bool: 67 if self.is_evaluated(): 68 return False if self._result else True 69 else: 70 return False 71 72 def is_pure(self) -> bool: 73 return self._pure 74 75 def eval(self) -> bool: 76 """Evaluate function with its argument. 77 78 * evaluate function 79 * cache results or exceptions if `pure == True` 80 * reevaluate if `pure == False` 81 82 """ 83 if not self.is_evaluated() or not self._pure: 84 try: 85 result = self._f(self._d) 86 except Exception as exc: 87 self._result = XOR(MB(), MB(exc)) 88 return False 89 else: 90 self._result = XOR(MB(result), MB()) 91 return True 92 if self: 93 return True 94 else: 95 return False 96 97 def result(self) -> MB[R]: 98 if not self.is_evaluated(): 99 self.eval() 100 101 if self._result: 102 return MB(self._result.getLeft()) 103 else: 104 return MB() 105 106 def exception(self) -> MB[Exception]: 107 if not self.is_evaluated(): 108 self.eval() 109 return self._result.getRight()
Delayed evaluation of a function mapping a value of type D
Class instance delays the executable of a function where Lazy(f, arg)
constructs an object that can evaluate the Callable f
with its argument
at a later time.
- first argument
f
taking values of type~D
to values of type~R
- second argument
arg: ~D
is the argument to be passed tof
- where the type
~D
is thetuple
type of the argument types tof
- where the type
- function is evaluated when the eval method is called
- result is cached unless
pure
is set toFalse
in__init__
method
Usually use case is to make a function "non-strict" by passing some of its arguments wrapped in Lazy instances.
def
eval(self) -> bool:
75 def eval(self) -> bool: 76 """Evaluate function with its argument. 77 78 * evaluate function 79 * cache results or exceptions if `pure == True` 80 * reevaluate if `pure == False` 81 82 """ 83 if not self.is_evaluated() or not self._pure: 84 try: 85 result = self._f(self._d) 86 except Exception as exc: 87 self._result = XOR(MB(), MB(exc)) 88 return False 89 else: 90 self._result = XOR(MB(result), MB()) 91 return True 92 if self: 93 return True 94 else: 95 return False
Evaluate function with its argument.
- evaluate function
- cache results or exceptions if
pure == True
- reevaluate if
pure == False
def
lazy( f: 'Callable[P, R]', *args: 'P.args', pure: bool = True) -> 'Lazy[tuple[P.args], R]':
111def lazy[R, **P](f: Callable[P, R], *args: P.args, pure: bool=True) -> Lazy[tuple[P.args], R]: 112 """Delayed evaluation of a function with arbitrary positional arguments. 113 114 Function returning a delayed evaluation of a function of an arbitrary number 115 of positional arguments. 116 117 * first positional argument `f` takes a function 118 * next positional arguments are the arguments to be applied later to `f` 119 * `f` is evaluated when the `eval` method of the returned Lazy is called 120 * `f` is evaluated only once with results cached unless `pure` is `False` 121 * if `pure` is false, the arguments are reapplied to `f` 122 * useful for repeating side effects 123 * when arguments are or contain shared references 124 125 """ 126 return Lazy(sequenced(f), args, pure=pure)
Delayed evaluation of a function with arbitrary positional arguments.
Function returning a delayed evaluation of a function of an arbitrary number of positional arguments.
- first positional argument
f
takes a function - next positional arguments are the arguments to be applied later to
f
f
is evaluated when theeval
method of the returned Lazy is calledf
is evaluated only once with results cached unlesspure
isFalse
- if
pure
is false, the arguments are reapplied tof
- useful for repeating side effects
- when arguments are or contain shared references