koatl 0.1.30__tar.gz → 0.1.31__tar.gz
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.
- {koatl-0.1.30 → koatl-0.1.31}/Cargo.lock +1 -1
- {koatl-0.1.30 → koatl-0.1.31}/PKG-INFO +1 -1
- {koatl-0.1.30 → koatl-0.1.31}/koatl/Cargo.toml +1 -1
- koatl-0.1.31/koatl/python/koatl/prelude/functional/__init__.tl +26 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/functional/algebra.tl +14 -4
- koatl-0.1.31/koatl/python/koatl/prelude/functional/env.tl +42 -0
- koatl-0.1.31/koatl/python/koatl/prelude/functional/memo.tl +121 -0
- {koatl-0.1.30 → koatl-0.1.31/koatl}/python/koatl/prelude/functional/result.tl +2 -2
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/runtime/__init__.py +1 -2
- koatl-0.1.31/koatl/tests/e2e/prelude/env.tl +9 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/memo.tl +29 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/transform.rs +1 -5
- koatl-0.1.31/python/koatl/prelude/functional/__init__.tl +26 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/functional/algebra.tl +14 -4
- koatl-0.1.31/python/koatl/prelude/functional/env.tl +42 -0
- koatl-0.1.31/python/koatl/prelude/functional/memo.tl +121 -0
- {koatl-0.1.30/koatl → koatl-0.1.31}/python/koatl/prelude/functional/result.tl +2 -2
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/runtime/__init__.py +1 -2
- koatl-0.1.30/koatl/python/koatl/prelude/functional/__init__.tl +0 -37
- koatl-0.1.30/koatl/python/koatl/prelude/functional/memo.tl +0 -83
- koatl-0.1.30/koatl/python/koatl/prelude/functional/reader.tl +0 -30
- koatl-0.1.30/koatl/tests/e2e/prelude/reader.tl +0 -11
- koatl-0.1.30/python/koatl/prelude/functional/__init__.tl +0 -37
- koatl-0.1.30/python/koatl/prelude/functional/memo.tl +0 -83
- koatl-0.1.30/python/koatl/prelude/functional/reader.tl +0 -30
- {koatl-0.1.30 → koatl-0.1.31}/Cargo.toml +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/README.md +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/.github/workflows/CI.yml +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/.gitignore +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/LICENSE +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/README.md +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/__init__.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/__main__.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/cli.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/notebook/__init__.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/notebook/magic.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/__init__.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/functional/async.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/functional/async_util.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/functional/list.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/io.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/iterable.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/runtime/classes.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/runtime/helpers.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/runtime/meta_finder.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/runtime/record.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/runtime/virtual.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/requirements.txt +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/src/emit_py.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/src/lib.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/coal.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/containers.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/decorators.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/destructure-for-and-fn.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/destructure.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/escape_ident.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/fstr.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/functions.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/generator.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/if_expr.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/imports.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/loops.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/match.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/nary-list.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/placeholder.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/precedence.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/record.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/scopes.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/semantic_whitespace.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/short_circuit.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/destructure.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/async.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/aug_assign.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/iterables.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/list.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/result.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/slice.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/try.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/virtual.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/util/__init__.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/util/module0.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/util/module1.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/util/module2.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/parse/arith.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/parse/assign.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/parse/block-comments.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/parse/deco.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/parse/func.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/parse/matches.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/test_e2e.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/test_parse.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/Cargo.toml +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/Cargo.toml +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/src/ast.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/src/lexer.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/src/lib.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/src/parser.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/src/util.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/tests/lexer.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/inference.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/lib.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/main.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/parse_timer.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/parser.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/py/ast.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/py/emit.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/py/mod.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/py/util.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/resolve_scopes.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/types.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/util.rs +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/pyproject.toml +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/__init__.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/__main__.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/cli.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/notebook/__init__.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/notebook/magic.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/__init__.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/functional/async.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/functional/async_util.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/functional/list.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/io.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/iterable.tl +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/runtime/classes.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/runtime/helpers.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/runtime/meta_finder.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/runtime/record.py +0 -0
- {koatl-0.1.30 → koatl-0.1.31}/python/koatl/runtime/virtual.py +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export import .algebra.*
|
|
2
|
+
export import .result.*
|
|
3
|
+
export import .async.*
|
|
4
|
+
export import .env.*
|
|
5
|
+
export import .memo.*
|
|
6
|
+
export import .list.*
|
|
7
|
+
|
|
8
|
+
export id = x => x
|
|
9
|
+
|
|
10
|
+
export compose = (*args) =>
|
|
11
|
+
args match:
|
|
12
|
+
[] => raise ValueError("At least one function is required for composition")
|
|
13
|
+
[f] => f
|
|
14
|
+
[*fs] =>
|
|
15
|
+
let composed = (*args, **kwargs) =>
|
|
16
|
+
let value = fs[-1](*args, **kwargs)
|
|
17
|
+
for f in fs[..-1..-1]:
|
|
18
|
+
value = f(value)
|
|
19
|
+
value
|
|
20
|
+
|
|
21
|
+
composed.__name__ = "<Fn.compose()>"
|
|
22
|
+
composed.__qualname__ = composed.__name__
|
|
23
|
+
composed.signature = fs[-1].signature
|
|
24
|
+
|
|
25
|
+
composed
|
|
26
|
+
default raise ValueError("Invalid arguments for Fn.compose()")
|
|
@@ -20,6 +20,7 @@ export Monad = class(Trait):
|
|
|
20
20
|
export MonadOnce = class(Monad, Trait):
|
|
21
21
|
"""
|
|
22
22
|
Abstract base class for monads that don't require branching.
|
|
23
|
+
In other words, a deterministic monad.
|
|
23
24
|
|
|
24
25
|
A MonadOnce only calls `f` in `bind_once` once, which is suitable for
|
|
25
26
|
use with Python generators that cannot be cloned or reused.
|
|
@@ -43,11 +44,20 @@ export MonadOnce = class(Monad, Trait):
|
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
export Identity = class(MonadOnce):
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
"""
|
|
48
|
+
A utility class for preventing `@` from wrapping the function output.
|
|
49
|
+
|
|
50
|
+
The semantics are unusual:
|
|
51
|
+
- pure returns the value directly, NOT wrapped in Identity.
|
|
52
|
+
- bind_once can be called on either Identity(value) or a bare value directly.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
__slots__ = ("value",)
|
|
56
|
+
__match_args__ = ("value",)
|
|
57
|
+
|
|
58
|
+
__init__ = (self, value) => self.value = value
|
|
49
59
|
|
|
50
|
-
bind_once = (self, f) => f(self)
|
|
60
|
+
bind_once = (self, f) => self matches Identity(value) then f(value) else f(self)
|
|
51
61
|
|
|
52
62
|
pure = staticmethod& x => x
|
|
53
63
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import functools.wraps
|
|
2
|
+
import .MonadOnce
|
|
3
|
+
|
|
4
|
+
export Env = class(MonadOnce):
|
|
5
|
+
__slots__ = ("f",)
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
A monad that provides a *mutable* reference to the environment.
|
|
9
|
+
This means that it can behave like a State, without needing to
|
|
10
|
+
explicitly write updates to the context.
|
|
11
|
+
|
|
12
|
+
Structurally, it is identical to Reader.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__init__ = (self, f) => self.f = f
|
|
16
|
+
|
|
17
|
+
__repr__ = self => "Env(...)"
|
|
18
|
+
|
|
19
|
+
run = (self, ctx) => self.f(ctx)
|
|
20
|
+
|
|
21
|
+
pure = staticmethod& value => Env(ctx => value)
|
|
22
|
+
|
|
23
|
+
get = None # to be populated later
|
|
24
|
+
|
|
25
|
+
item = staticmethod& name => Env(ctx => ctx[name])
|
|
26
|
+
|
|
27
|
+
bind_once = (self, f) => Env& ctx =>
|
|
28
|
+
let v = f(self.f(ctx))
|
|
29
|
+
if v matches Env():
|
|
30
|
+
return v.f(ctx)
|
|
31
|
+
v
|
|
32
|
+
|
|
33
|
+
bind_gen = (self, gen) => Env& ctx =>
|
|
34
|
+
try:
|
|
35
|
+
while True:
|
|
36
|
+
self = gen.send(self.f(ctx))
|
|
37
|
+
except StopIteration(value=value):
|
|
38
|
+
return value
|
|
39
|
+
|
|
40
|
+
NoKey = object()
|
|
41
|
+
|
|
42
|
+
Env.get = Env(ctx => ctx)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import collections.defaultdict
|
|
2
|
+
import functools.wraps
|
|
3
|
+
|
|
4
|
+
import .algebra.(MonadOnce, Identity)
|
|
5
|
+
import .result.(Result, Ok, Err)
|
|
6
|
+
import .env.Env
|
|
7
|
+
|
|
8
|
+
export Memo = class(Env):
|
|
9
|
+
__slots__ = ("f",)
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
The Memo monad is a specialization of Env, where
|
|
13
|
+
the environment is a cache that stores previously computed values.
|
|
14
|
+
It is used to memoize computations, allowing for efficient reuse of results
|
|
15
|
+
without recomputing them.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
Cache = class:
|
|
19
|
+
"""
|
|
20
|
+
Basic implementation of a cache without any eviction policy.
|
|
21
|
+
"""
|
|
22
|
+
__init__ = self =>
|
|
23
|
+
self.cache = defaultdict(dict)
|
|
24
|
+
|
|
25
|
+
__repr__ = self => f"Memo.Cache({self.cache})"
|
|
26
|
+
|
|
27
|
+
try_get = (self, name, deps) => try self.cache[name][deps] except KeyError()
|
|
28
|
+
|
|
29
|
+
update = (self, name, deps, value) =>
|
|
30
|
+
self.cache[name][deps] = value
|
|
31
|
+
value
|
|
32
|
+
|
|
33
|
+
NullCache = class:
|
|
34
|
+
"""
|
|
35
|
+
A cache that does not store any values.
|
|
36
|
+
Useful for testing or when memoization is not needed.
|
|
37
|
+
"""
|
|
38
|
+
__init__ = self => None
|
|
39
|
+
|
|
40
|
+
__repr__ = self => "Memo.NullCache()"
|
|
41
|
+
|
|
42
|
+
try_get = (self, name, deps) => Err()
|
|
43
|
+
|
|
44
|
+
update = (self, name, deps, value) => value
|
|
45
|
+
|
|
46
|
+
__init__ = (self, f) => self.f = f
|
|
47
|
+
|
|
48
|
+
__repr__ = self => f"Memo(...)"
|
|
49
|
+
|
|
50
|
+
# We need to handle
|
|
51
|
+
# memo @value
|
|
52
|
+
# separately, because we *don't* want to memoize the monad itself
|
|
53
|
+
# but rather the value it produces.
|
|
54
|
+
# Another way to think about it is that
|
|
55
|
+
# fused_value produces a Memo.T<MonadOnce<T>> value.
|
|
56
|
+
value = staticmethod& (id, deps, f) =>
|
|
57
|
+
Memo(ctx =>
|
|
58
|
+
if ctx.try_get(id, tuple(deps)) matches Ok((inner_type, value)):
|
|
59
|
+
if inner_type === None or inner_type === Memo:
|
|
60
|
+
# Non-monadic cached values, or Memo cached values, shouldn't
|
|
61
|
+
# wrap the output in a monadic type.
|
|
62
|
+
|
|
63
|
+
# The issue is that the present do-block expects a value to be bound,
|
|
64
|
+
# but we don't want to use @Memo.pure(value) because that would
|
|
65
|
+
# produce a Memo<Memo<T>> value.
|
|
66
|
+
# It's hacky, but it works for now.
|
|
67
|
+
return @Identity(value)
|
|
68
|
+
else:
|
|
69
|
+
# This gives us a Memo<MonadOnce<T>> value as normal.
|
|
70
|
+
return @inner_type.pure(value)
|
|
71
|
+
|
|
72
|
+
let inner = f()
|
|
73
|
+
let inner_type = type(inner)
|
|
74
|
+
|
|
75
|
+
if isinstance(inner, Memo):
|
|
76
|
+
inner = @Identity(inner.f(ctx))
|
|
77
|
+
else if not hasattr(inner, "bind_once"):
|
|
78
|
+
inner_type = None
|
|
79
|
+
inner = @Identity(inner)
|
|
80
|
+
else:
|
|
81
|
+
inner = @inner
|
|
82
|
+
|
|
83
|
+
ctx.update(id, tuple(deps), (inner_type, inner))
|
|
84
|
+
inner
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
fn = staticmethod& f => wraps(f)& (*args, **kwargs) =>
|
|
88
|
+
let id = f"{f.__module__}.{f.__qualname__}"
|
|
89
|
+
let deps = (tuple(args), tuple(kwargs.items()))
|
|
90
|
+
|
|
91
|
+
# Same semantics as Memo.value above.
|
|
92
|
+
Memo.value(id, deps, () => f(*args, **kwargs))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
run = (self, ctx=None) =>
|
|
96
|
+
if ctx === None:
|
|
97
|
+
ctx = Memo.Cache()
|
|
98
|
+
self.f(ctx)
|
|
99
|
+
|
|
100
|
+
pure = staticmethod& value => Memo(ctx => value)
|
|
101
|
+
|
|
102
|
+
bind_once = (self, f) => Memo(ctx =>
|
|
103
|
+
let value = f(self.f(ctx))
|
|
104
|
+
if value matches Memo():
|
|
105
|
+
return value.f(ctx)
|
|
106
|
+
value
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
bind_gen = (self, gen) => Memo(ctx =>
|
|
110
|
+
self = self.f(ctx)
|
|
111
|
+
try:
|
|
112
|
+
while True:
|
|
113
|
+
self = gen.send(self)
|
|
114
|
+
if self matches Memo():
|
|
115
|
+
self = self.f(ctx)
|
|
116
|
+
except StopIteration(value=value):
|
|
117
|
+
return value
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
__tl__.memo_value = Memo.value
|
|
@@ -102,8 +102,8 @@ export Ok = class(Result):
|
|
|
102
102
|
__init__ = (self, value) =>
|
|
103
103
|
if hasattr(self, "value"):
|
|
104
104
|
# This is necessary to prevent overwriting the value
|
|
105
|
-
# since Python's Result.__new__ will call Ok.__init__ again
|
|
106
|
-
#
|
|
105
|
+
# since Python's Result.__new__ will call Ok.__init__ again,
|
|
106
|
+
# in the case we call Result(Ok(value))
|
|
107
107
|
return None
|
|
108
108
|
self.value = value
|
|
109
109
|
|
|
@@ -43,12 +43,11 @@ __tl__ = SimpleNamespace(
|
|
|
43
43
|
# These require more complex logic and require the prelude.
|
|
44
44
|
# The runtime provides dummy implementations that raise if used without the prelude.
|
|
45
45
|
memo_value=dummy("memo"),
|
|
46
|
-
bind_memo_value=dummy("memo"),
|
|
47
46
|
op_map=dummy("?"),
|
|
48
47
|
op_coal=("??"),
|
|
49
48
|
Ok=dummy("try-expr"),
|
|
50
49
|
Err=dummy("try-expr"),
|
|
51
|
-
Result=dummy("
|
|
50
|
+
Result=dummy("Result"),
|
|
52
51
|
**{name: helpers.__dict__[name] for name in helpers.__all__},
|
|
53
52
|
**{name: record.__dict__[name] for name in record.__all__},
|
|
54
53
|
**{name: virtual.__dict__[name] for name in virtual.__all__},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import util.assert_eq
|
|
2
|
+
import time
|
|
2
3
|
|
|
3
4
|
counts = 0
|
|
4
5
|
|
|
@@ -46,3 +47,31 @@ assert_eq(f(10, 20).run(ctx), 200)
|
|
|
46
47
|
assert_eq(f(10, 30).run(ctx), 300)
|
|
47
48
|
assert_eq(f(10, 30).run(ctx), 300)
|
|
48
49
|
assert_eq(counts, 2)
|
|
50
|
+
|
|
51
|
+
fib = x => x < 2 then @Memo.pure(1) else memo @fib(x - 1) + @fib(x - 2)
|
|
52
|
+
|
|
53
|
+
now = time.time()
|
|
54
|
+
fib(35).run()
|
|
55
|
+
|
|
56
|
+
# Improperly memoized fib will take much longer to run.
|
|
57
|
+
assert_eq(time.time() - now < 0.1, True)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Memo.fn with nested monad
|
|
61
|
+
|
|
62
|
+
f = Memo.fn& () =>
|
|
63
|
+
@Async.sleep(0.1)
|
|
64
|
+
123
|
|
65
|
+
|
|
66
|
+
now = time.time()
|
|
67
|
+
ctx = Memo.Cache()
|
|
68
|
+
assert_eq(f().run(ctx).run(), 123)
|
|
69
|
+
assert_eq(f().run(ctx).run(), 123)
|
|
70
|
+
assert_eq(time.time() - now < 0.12, True)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
now = time.time()
|
|
74
|
+
ctx = Memo.NullCache()
|
|
75
|
+
assert_eq(f().run(ctx).run(), 123)
|
|
76
|
+
assert_eq(f().run(ctx).run(), 123)
|
|
77
|
+
assert_eq(time.time() - now > 0.19, True)
|
|
@@ -2031,11 +2031,7 @@ impl<'src, 'ast> SExprExt<'src, 'ast> for SExpr<'src> {
|
|
|
2031
2031
|
let linecol = ctx.line_cache.linecol(span.start);
|
|
2032
2032
|
|
|
2033
2033
|
let memo_call = a.call(
|
|
2034
|
-
|
|
2035
|
-
a.tl_builtin("bind_memo_value")
|
|
2036
|
-
} else {
|
|
2037
|
-
a.tl_builtin("memo_value")
|
|
2038
|
-
},
|
|
2034
|
+
a.tl_builtin("memo_value"),
|
|
2039
2035
|
vec![
|
|
2040
2036
|
PyCallItem::Arg(a.str(format!(
|
|
2041
2037
|
"{}:{}:{}:{:08x}",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export import .algebra.*
|
|
2
|
+
export import .result.*
|
|
3
|
+
export import .async.*
|
|
4
|
+
export import .env.*
|
|
5
|
+
export import .memo.*
|
|
6
|
+
export import .list.*
|
|
7
|
+
|
|
8
|
+
export id = x => x
|
|
9
|
+
|
|
10
|
+
export compose = (*args) =>
|
|
11
|
+
args match:
|
|
12
|
+
[] => raise ValueError("At least one function is required for composition")
|
|
13
|
+
[f] => f
|
|
14
|
+
[*fs] =>
|
|
15
|
+
let composed = (*args, **kwargs) =>
|
|
16
|
+
let value = fs[-1](*args, **kwargs)
|
|
17
|
+
for f in fs[..-1..-1]:
|
|
18
|
+
value = f(value)
|
|
19
|
+
value
|
|
20
|
+
|
|
21
|
+
composed.__name__ = "<Fn.compose()>"
|
|
22
|
+
composed.__qualname__ = composed.__name__
|
|
23
|
+
composed.signature = fs[-1].signature
|
|
24
|
+
|
|
25
|
+
composed
|
|
26
|
+
default raise ValueError("Invalid arguments for Fn.compose()")
|
|
@@ -20,6 +20,7 @@ export Monad = class(Trait):
|
|
|
20
20
|
export MonadOnce = class(Monad, Trait):
|
|
21
21
|
"""
|
|
22
22
|
Abstract base class for monads that don't require branching.
|
|
23
|
+
In other words, a deterministic monad.
|
|
23
24
|
|
|
24
25
|
A MonadOnce only calls `f` in `bind_once` once, which is suitable for
|
|
25
26
|
use with Python generators that cannot be cloned or reused.
|
|
@@ -43,11 +44,20 @@ export MonadOnce = class(Monad, Trait):
|
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
export Identity = class(MonadOnce):
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
"""
|
|
48
|
+
A utility class for preventing `@` from wrapping the function output.
|
|
49
|
+
|
|
50
|
+
The semantics are unusual:
|
|
51
|
+
- pure returns the value directly, NOT wrapped in Identity.
|
|
52
|
+
- bind_once can be called on either Identity(value) or a bare value directly.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
__slots__ = ("value",)
|
|
56
|
+
__match_args__ = ("value",)
|
|
57
|
+
|
|
58
|
+
__init__ = (self, value) => self.value = value
|
|
49
59
|
|
|
50
|
-
bind_once = (self, f) => f(self)
|
|
60
|
+
bind_once = (self, f) => self matches Identity(value) then f(value) else f(self)
|
|
51
61
|
|
|
52
62
|
pure = staticmethod& x => x
|
|
53
63
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import functools.wraps
|
|
2
|
+
import .MonadOnce
|
|
3
|
+
|
|
4
|
+
export Env = class(MonadOnce):
|
|
5
|
+
__slots__ = ("f",)
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
A monad that provides a *mutable* reference to the environment.
|
|
9
|
+
This means that it can behave like a State, without needing to
|
|
10
|
+
explicitly write updates to the context.
|
|
11
|
+
|
|
12
|
+
Structurally, it is identical to Reader.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__init__ = (self, f) => self.f = f
|
|
16
|
+
|
|
17
|
+
__repr__ = self => "Env(...)"
|
|
18
|
+
|
|
19
|
+
run = (self, ctx) => self.f(ctx)
|
|
20
|
+
|
|
21
|
+
pure = staticmethod& value => Env(ctx => value)
|
|
22
|
+
|
|
23
|
+
get = None # to be populated later
|
|
24
|
+
|
|
25
|
+
item = staticmethod& name => Env(ctx => ctx[name])
|
|
26
|
+
|
|
27
|
+
bind_once = (self, f) => Env& ctx =>
|
|
28
|
+
let v = f(self.f(ctx))
|
|
29
|
+
if v matches Env():
|
|
30
|
+
return v.f(ctx)
|
|
31
|
+
v
|
|
32
|
+
|
|
33
|
+
bind_gen = (self, gen) => Env& ctx =>
|
|
34
|
+
try:
|
|
35
|
+
while True:
|
|
36
|
+
self = gen.send(self.f(ctx))
|
|
37
|
+
except StopIteration(value=value):
|
|
38
|
+
return value
|
|
39
|
+
|
|
40
|
+
NoKey = object()
|
|
41
|
+
|
|
42
|
+
Env.get = Env(ctx => ctx)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import collections.defaultdict
|
|
2
|
+
import functools.wraps
|
|
3
|
+
|
|
4
|
+
import .algebra.(MonadOnce, Identity)
|
|
5
|
+
import .result.(Result, Ok, Err)
|
|
6
|
+
import .env.Env
|
|
7
|
+
|
|
8
|
+
export Memo = class(Env):
|
|
9
|
+
__slots__ = ("f",)
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
The Memo monad is a specialization of Env, where
|
|
13
|
+
the environment is a cache that stores previously computed values.
|
|
14
|
+
It is used to memoize computations, allowing for efficient reuse of results
|
|
15
|
+
without recomputing them.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
Cache = class:
|
|
19
|
+
"""
|
|
20
|
+
Basic implementation of a cache without any eviction policy.
|
|
21
|
+
"""
|
|
22
|
+
__init__ = self =>
|
|
23
|
+
self.cache = defaultdict(dict)
|
|
24
|
+
|
|
25
|
+
__repr__ = self => f"Memo.Cache({self.cache})"
|
|
26
|
+
|
|
27
|
+
try_get = (self, name, deps) => try self.cache[name][deps] except KeyError()
|
|
28
|
+
|
|
29
|
+
update = (self, name, deps, value) =>
|
|
30
|
+
self.cache[name][deps] = value
|
|
31
|
+
value
|
|
32
|
+
|
|
33
|
+
NullCache = class:
|
|
34
|
+
"""
|
|
35
|
+
A cache that does not store any values.
|
|
36
|
+
Useful for testing or when memoization is not needed.
|
|
37
|
+
"""
|
|
38
|
+
__init__ = self => None
|
|
39
|
+
|
|
40
|
+
__repr__ = self => "Memo.NullCache()"
|
|
41
|
+
|
|
42
|
+
try_get = (self, name, deps) => Err()
|
|
43
|
+
|
|
44
|
+
update = (self, name, deps, value) => value
|
|
45
|
+
|
|
46
|
+
__init__ = (self, f) => self.f = f
|
|
47
|
+
|
|
48
|
+
__repr__ = self => f"Memo(...)"
|
|
49
|
+
|
|
50
|
+
# We need to handle
|
|
51
|
+
# memo @value
|
|
52
|
+
# separately, because we *don't* want to memoize the monad itself
|
|
53
|
+
# but rather the value it produces.
|
|
54
|
+
# Another way to think about it is that
|
|
55
|
+
# fused_value produces a Memo.T<MonadOnce<T>> value.
|
|
56
|
+
value = staticmethod& (id, deps, f) =>
|
|
57
|
+
Memo(ctx =>
|
|
58
|
+
if ctx.try_get(id, tuple(deps)) matches Ok((inner_type, value)):
|
|
59
|
+
if inner_type === None or inner_type === Memo:
|
|
60
|
+
# Non-monadic cached values, or Memo cached values, shouldn't
|
|
61
|
+
# wrap the output in a monadic type.
|
|
62
|
+
|
|
63
|
+
# The issue is that the present do-block expects a value to be bound,
|
|
64
|
+
# but we don't want to use @Memo.pure(value) because that would
|
|
65
|
+
# produce a Memo<Memo<T>> value.
|
|
66
|
+
# It's hacky, but it works for now.
|
|
67
|
+
return @Identity(value)
|
|
68
|
+
else:
|
|
69
|
+
# This gives us a Memo<MonadOnce<T>> value as normal.
|
|
70
|
+
return @inner_type.pure(value)
|
|
71
|
+
|
|
72
|
+
let inner = f()
|
|
73
|
+
let inner_type = type(inner)
|
|
74
|
+
|
|
75
|
+
if isinstance(inner, Memo):
|
|
76
|
+
inner = @Identity(inner.f(ctx))
|
|
77
|
+
else if not hasattr(inner, "bind_once"):
|
|
78
|
+
inner_type = None
|
|
79
|
+
inner = @Identity(inner)
|
|
80
|
+
else:
|
|
81
|
+
inner = @inner
|
|
82
|
+
|
|
83
|
+
ctx.update(id, tuple(deps), (inner_type, inner))
|
|
84
|
+
inner
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
fn = staticmethod& f => wraps(f)& (*args, **kwargs) =>
|
|
88
|
+
let id = f"{f.__module__}.{f.__qualname__}"
|
|
89
|
+
let deps = (tuple(args), tuple(kwargs.items()))
|
|
90
|
+
|
|
91
|
+
# Same semantics as Memo.value above.
|
|
92
|
+
Memo.value(id, deps, () => f(*args, **kwargs))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
run = (self, ctx=None) =>
|
|
96
|
+
if ctx === None:
|
|
97
|
+
ctx = Memo.Cache()
|
|
98
|
+
self.f(ctx)
|
|
99
|
+
|
|
100
|
+
pure = staticmethod& value => Memo(ctx => value)
|
|
101
|
+
|
|
102
|
+
bind_once = (self, f) => Memo(ctx =>
|
|
103
|
+
let value = f(self.f(ctx))
|
|
104
|
+
if value matches Memo():
|
|
105
|
+
return value.f(ctx)
|
|
106
|
+
value
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
bind_gen = (self, gen) => Memo(ctx =>
|
|
110
|
+
self = self.f(ctx)
|
|
111
|
+
try:
|
|
112
|
+
while True:
|
|
113
|
+
self = gen.send(self)
|
|
114
|
+
if self matches Memo():
|
|
115
|
+
self = self.f(ctx)
|
|
116
|
+
except StopIteration(value=value):
|
|
117
|
+
return value
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
__tl__.memo_value = Memo.value
|
|
@@ -102,8 +102,8 @@ export Ok = class(Result):
|
|
|
102
102
|
__init__ = (self, value) =>
|
|
103
103
|
if hasattr(self, "value"):
|
|
104
104
|
# This is necessary to prevent overwriting the value
|
|
105
|
-
# since Python's Result.__new__ will call Ok.__init__ again
|
|
106
|
-
#
|
|
105
|
+
# since Python's Result.__new__ will call Ok.__init__ again,
|
|
106
|
+
# in the case we call Result(Ok(value))
|
|
107
107
|
return None
|
|
108
108
|
self.value = value
|
|
109
109
|
|
|
@@ -43,12 +43,11 @@ __tl__ = SimpleNamespace(
|
|
|
43
43
|
# These require more complex logic and require the prelude.
|
|
44
44
|
# The runtime provides dummy implementations that raise if used without the prelude.
|
|
45
45
|
memo_value=dummy("memo"),
|
|
46
|
-
bind_memo_value=dummy("memo"),
|
|
47
46
|
op_map=dummy("?"),
|
|
48
47
|
op_coal=("??"),
|
|
49
48
|
Ok=dummy("try-expr"),
|
|
50
49
|
Err=dummy("try-expr"),
|
|
51
|
-
Result=dummy("
|
|
50
|
+
Result=dummy("Result"),
|
|
52
51
|
**{name: helpers.__dict__[name] for name in helpers.__all__},
|
|
53
52
|
**{name: record.__dict__[name] for name in record.__all__},
|
|
54
53
|
**{name: virtual.__dict__[name] for name in virtual.__all__},
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
export import .algebra.*
|
|
2
|
-
export import .result.*
|
|
3
|
-
export import .async.*
|
|
4
|
-
export import .reader.*
|
|
5
|
-
export import .memo.*
|
|
6
|
-
export import .list.*
|
|
7
|
-
|
|
8
|
-
named = name => fn =>
|
|
9
|
-
fn.__name__ = name
|
|
10
|
-
fn.__qualname__ = name
|
|
11
|
-
fn
|
|
12
|
-
|
|
13
|
-
methods = {
|
|
14
|
-
id: named("id")& x => x
|
|
15
|
-
|
|
16
|
-
compose: named("compose")& (*args) =>
|
|
17
|
-
args match:
|
|
18
|
-
[] => raise ValueError("At least one function is required for composition")
|
|
19
|
-
[f] => f
|
|
20
|
-
[*fs] =>
|
|
21
|
-
let composed = (*args, **kwargs) =>
|
|
22
|
-
let value = fs[-1](*args, **kwargs)
|
|
23
|
-
for f in fs[..-1..-1]:
|
|
24
|
-
value = f(value)
|
|
25
|
-
value
|
|
26
|
-
|
|
27
|
-
composed.__name__ = "<Fn.compose()>"
|
|
28
|
-
composed.__qualname__ = composed.__name__
|
|
29
|
-
composed.signature = fs[-1].signature
|
|
30
|
-
|
|
31
|
-
composed
|
|
32
|
-
default raise ValueError("Invalid arguments for Fn.compose()")
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
globals().update(methods)
|
|
36
|
-
|
|
37
|
-
__all__ = methods.keys() | tuple
|