GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — unpythonic/unpythonic/lispylet.py at master · Technologicat/unpythonic Expand file tree/ Copy pathlispylet.pyMore file actions 272 lines (212 loc) · 9.49 KB / Copy pathlispylet.pyFile metadata and controls272 lines (212 loc) · 9.49 KB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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 60 61 62 63 64 65 66 67 68 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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 # -*- coding: utf-8 -*- """Introduce local bindings. Lispy syntax.""" __all__ = ["let", "letrec", "dlet", "dletrec", "blet", "bletrec"] from collections.abc import Callable from functools import wraps from typing import Any, TypeVar from .arity import arity_includes, UnknownArity from .env import env as _envcls from .funutil import call F = TypeVar('F', bound=Callable) def let(bindings: tuple[tuple[str, Any], ...], body: Callable) -> Any: """``let`` expression. In ``let``, the bindings are independent (do not see each other); only the body may refer to the bindings. Order-preserving list uniqifier:: u = lambda lst: let((("seen", set()),), lambda e: [e.seen.add(x) or x for x in lst if x not in e.seen]) Bindings must be a sequence even if there is just one binding. A lambda using a locally defined lambda as a helper:: g = let((("square", lambda y: y**2),), lambda e: lambda x: 42 * e.square(x)) g(10) # --> 4200 Unlike in ``letrec``, no ``lambda e: ...`` is needed for ``square``. Because in ``let``, the bindings do not see the environment, there is no risk of misunderstanding the lambda in the environment initialization procedure. Composability. As Lisp programmers know, the second example is subtly different from:: g = lambda x: let((("square", lambda y: y**2),), lambda e: 42 * e.square(x)) We only moved the ``lambda x:``. In the original version, the let expression runs just once, when ``g`` is defined, whereas in this one, it re-runs whenever ``g`` is called. *Let over lambda*. The inner lambda is the definition of the function ``counter``:: from unpythonic.seq import begin counter = let((("x", 0),), lambda e: lambda: begin(e.set("x", e.x + 1), e.x)) counter() # --> 1 counter() # --> 2 counter() # --> 3 Parameters: `bindings`: sequence of `(name, value)` pairs `name`: str `value`: anything Each argument name is bound to its value. Unlike in ``letrec``, no ``lambda e: ...`` is needed, as the environment is not seen by the bindings. `body`: function One-argument function to run, taking an `env` instance that contains the ``let`` bindings as its attributes. The environment is passed as the first positional argument. If you need to pass in more stuff: - Use the closure property (free variables, lexical scoping). - Make a nested lambda, like above. Only the outermost one is "eaten" by the environment initialization procedure. Returns: The return value of ``body``. """ return _let(bindings, body) def letrec(bindings: tuple[tuple[str, Any], ...], body: Callable) -> Any: """``letrec`` expression. Like ``let``, but bindings can see each other. To make a binding use the value of an earlier one, use a ``lambda e: ...``:: letrec((('a', 1), ('b', lambda e: e.a + 1)), lambda e: e.b) # --> 2 Each RHS is evaluated just once, and the result is bound to the name on the LHS. So even if 'a' is ``e.set()`` to a different value later, 'b' **won't** be updated. If your value is callable, wrap it in a ``lambda e: ...`` even if it doesn't need the environment, like this:: letrec((('a', 1), # just a value, doesn't use env ('b', lambda e: e.a + 1), # just a value, uses env ('f', lambda e: lambda x: # callable, whether or not uses env 42*x)), lambda e: e.b * e.f(1)) # --> 84 A callable value may depend on **any** binding, also later ones. This allows mutually recursive functions:: letrec((('evenp', lambda e: lambda x: (x == 0) or e.oddp(x - 1)), ('oddp', lambda e: lambda x: (x != 0) and e.evenp(x - 1))), lambda e: e.evenp(42)) # --> True Order-preserving list uniqifier:: from unpythonic.seq import begin u = lambda lst: letrec((("seen", set()), ("see", lambda e: lambda x: begin(e.seen.add(x), x))), lambda e: [e.see(x) for x in lst if x not in e.seen]) L = [1, 1, 3, 1, 3, 2, 3, 2, 2, 2, 4, 4, 1, 2, 3] print(u(L)) # [1, 3, 2, 4] Parameters: `bindings`: sequence of `(name, value)` pairs `name`: str The name to which the value will be bound. `value`: anything Either a simple value (non-callable, doesn't use the environment), or an expression of the form ``lambda e: valexpr``, providing access to the environment as ``e``. If ``valexpr`` itself is callable, the value **must** have the ``lambda e: ...`` wrapper to prevent any misunderstandings in the environment initialization procedure. `body`: function Like in ``let``. Returns: The return value of ``body``. """ return _let(bindings, body, mode="letrec") def dlet(bindings: tuple[tuple[str, Any], ...]) -> Callable[[F], F]: """``let`` decorator. For let-over-def; think *let over lambda* in Lisp:: @dlet((('x', 17),)) def f(*, env): return env.x f() # --> 17 ``dlet`` provides a local storage that persists across calls:: @dlet((('count', 0),)) def counter(*, env): env.count += 1 return env.count counter() # --> 1 counter() # --> 2 counter() # --> 3 The named argument `env` is an env instance that contains the let bindings; all other args and kwargs are passed through. """ return _dlet(bindings) def dletrec(bindings: tuple[tuple[str, Any], ...]) -> Callable[[F], F]: """``letrec`` decorator. Like ``dlet``, but with ``letrec`` instead of ``let``:: @dletrec((('x', 2), ('y', lambda e: e.x + 3))) def bar(a, *, env): return a + env.y bar(10) # --> 15 """ return _dlet(bindings, mode="letrec") def blet(bindings: tuple[tuple[str, Any], ...]) -> Callable[[Callable], Any]: """``let`` block. This chains ``@dlet`` and ``@call``:: @blet((('x', 9001),)) def result(*, env): return env.x print(result) # --> 9001 """ return _blet(bindings) def bletrec(bindings: tuple[tuple[str, Any], ...]) -> Callable[[Callable], Any]: """``letrec`` block. This chains ``@dletrec`` and ``@call``.""" return _blet(bindings, mode="letrec") # Core idea based on StackOverflow answer by divs1210 (2017), # used under the MIT license. https://stackoverflow.com/a/44737147 def _let(bindings: tuple[tuple[str, Any], ...], body: Callable | None, *, env: _envcls | None = None, mode: str = "let") -> Any: assert mode in ("let", "letrec") env = env or _envcls() if not bindings: env.finalize() if body: if not callable(body): raise TypeError(f"Expected callable body, got {type(body)} with value {repr(body)}") try: if not arity_includes(body, 1): raise TypeError("Arity mismatch; body must allow arity 1, to take in the environment.") except UnknownArity: # pragma: no cover; well, we tried! pass # decorators need just the final env; else run body now return env if body is None else body(env) (k, v), *more = bindings if k in env: raise AttributeError(f"Cannot rebind the same name {repr(k)} in a {mode} initializer list") if mode == "letrec" and callable(v): try: if not arity_includes(v, 1): raise TypeError("Arity mismatch; callable value must allow arity 1, to take in the environment.") except UnknownArity: # pragma: no cover; well, we tried! pass v = v(env) env[k] = v return _let(more, body, env=env, mode=mode) # loop # _envname is for co-operation with the dlet macro. def _dlet(bindings: tuple[tuple[str, Any], ...], mode: str = "let", _envname: str = "env") -> Callable[[F], F]: # let and letrec decorator factory def deco(body: F) -> F: env = _let(bindings, body=None, mode=mode) # set up env, don't run yet @wraps(body) def withenv(*args: Any, **kwargs: Any) -> Any: kwargs_with_env = kwargs.copy() kwargs_with_env[_envname] = env return body(*args, **kwargs_with_env) return withenv return deco def _blet(bindings: tuple[tuple[str, Any], ...], mode: str = "let", _envname: str = "env") -> Callable[[Callable], Any]: dlet_deco = _dlet(bindings, mode, _envname) def deco(body: Callable) -> Any: return call(dlet_deco(body)) return deco |
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |