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.
Files changed (128) hide show
  1. {koatl-0.1.30 → koatl-0.1.31}/Cargo.lock +1 -1
  2. {koatl-0.1.30 → koatl-0.1.31}/PKG-INFO +1 -1
  3. {koatl-0.1.30 → koatl-0.1.31}/koatl/Cargo.toml +1 -1
  4. koatl-0.1.31/koatl/python/koatl/prelude/functional/__init__.tl +26 -0
  5. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/functional/algebra.tl +14 -4
  6. koatl-0.1.31/koatl/python/koatl/prelude/functional/env.tl +42 -0
  7. koatl-0.1.31/koatl/python/koatl/prelude/functional/memo.tl +121 -0
  8. {koatl-0.1.30 → koatl-0.1.31/koatl}/python/koatl/prelude/functional/result.tl +2 -2
  9. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/runtime/__init__.py +1 -2
  10. koatl-0.1.31/koatl/tests/e2e/prelude/env.tl +9 -0
  11. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/memo.tl +29 -0
  12. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/transform.rs +1 -5
  13. koatl-0.1.31/python/koatl/prelude/functional/__init__.tl +26 -0
  14. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/functional/algebra.tl +14 -4
  15. koatl-0.1.31/python/koatl/prelude/functional/env.tl +42 -0
  16. koatl-0.1.31/python/koatl/prelude/functional/memo.tl +121 -0
  17. {koatl-0.1.30/koatl → koatl-0.1.31}/python/koatl/prelude/functional/result.tl +2 -2
  18. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/runtime/__init__.py +1 -2
  19. koatl-0.1.30/koatl/python/koatl/prelude/functional/__init__.tl +0 -37
  20. koatl-0.1.30/koatl/python/koatl/prelude/functional/memo.tl +0 -83
  21. koatl-0.1.30/koatl/python/koatl/prelude/functional/reader.tl +0 -30
  22. koatl-0.1.30/koatl/tests/e2e/prelude/reader.tl +0 -11
  23. koatl-0.1.30/python/koatl/prelude/functional/__init__.tl +0 -37
  24. koatl-0.1.30/python/koatl/prelude/functional/memo.tl +0 -83
  25. koatl-0.1.30/python/koatl/prelude/functional/reader.tl +0 -30
  26. {koatl-0.1.30 → koatl-0.1.31}/Cargo.toml +0 -0
  27. {koatl-0.1.30 → koatl-0.1.31}/README.md +0 -0
  28. {koatl-0.1.30 → koatl-0.1.31}/koatl/.github/workflows/CI.yml +0 -0
  29. {koatl-0.1.30 → koatl-0.1.31}/koatl/.gitignore +0 -0
  30. {koatl-0.1.30 → koatl-0.1.31}/koatl/LICENSE +0 -0
  31. {koatl-0.1.30 → koatl-0.1.31}/koatl/README.md +0 -0
  32. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/__init__.py +0 -0
  33. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/__main__.py +0 -0
  34. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/cli.py +0 -0
  35. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/notebook/__init__.py +0 -0
  36. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/notebook/magic.py +0 -0
  37. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/__init__.tl +0 -0
  38. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/functional/async.tl +0 -0
  39. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/functional/async_util.py +0 -0
  40. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/functional/list.tl +0 -0
  41. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/io.tl +0 -0
  42. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/prelude/iterable.tl +0 -0
  43. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/runtime/classes.py +0 -0
  44. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/runtime/helpers.py +0 -0
  45. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/runtime/meta_finder.py +0 -0
  46. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/runtime/record.py +0 -0
  47. {koatl-0.1.30 → koatl-0.1.31}/koatl/python/koatl/runtime/virtual.py +0 -0
  48. {koatl-0.1.30 → koatl-0.1.31}/koatl/requirements.txt +0 -0
  49. {koatl-0.1.30 → koatl-0.1.31}/koatl/src/emit_py.rs +0 -0
  50. {koatl-0.1.30 → koatl-0.1.31}/koatl/src/lib.rs +0 -0
  51. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/coal.tl +0 -0
  52. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/containers.tl +0 -0
  53. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/decorators.tl +0 -0
  54. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/destructure-for-and-fn.tl +0 -0
  55. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/destructure.tl +0 -0
  56. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/escape_ident.tl +0 -0
  57. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/fstr.tl +0 -0
  58. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/functions.tl +0 -0
  59. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/generator.tl +0 -0
  60. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/if_expr.tl +0 -0
  61. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/imports.tl +0 -0
  62. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/loops.tl +0 -0
  63. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/match.tl +0 -0
  64. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/nary-list.tl +0 -0
  65. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/placeholder.tl +0 -0
  66. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/precedence.tl +0 -0
  67. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/record.tl +0 -0
  68. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/scopes.tl +0 -0
  69. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/semantic_whitespace.tl +0 -0
  70. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/base/short_circuit.tl +0 -0
  71. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/destructure.tl +0 -0
  72. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/async.tl +0 -0
  73. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/aug_assign.tl +0 -0
  74. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/iterables.tl +0 -0
  75. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/list.tl +0 -0
  76. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/result.tl +0 -0
  77. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/slice.tl +0 -0
  78. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/try.tl +0 -0
  79. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/prelude/virtual.tl +0 -0
  80. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/util/__init__.py +0 -0
  81. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/util/module0.tl +0 -0
  82. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/util/module1.tl +0 -0
  83. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/e2e/util/module2.tl +0 -0
  84. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/parse/arith.tl +0 -0
  85. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/parse/assign.tl +0 -0
  86. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/parse/block-comments.tl +0 -0
  87. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/parse/deco.tl +0 -0
  88. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/parse/func.tl +0 -0
  89. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/parse/matches.tl +0 -0
  90. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/test_e2e.py +0 -0
  91. {koatl-0.1.30 → koatl-0.1.31}/koatl/tests/test_parse.py +0 -0
  92. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/Cargo.toml +0 -0
  93. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/Cargo.toml +0 -0
  94. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/src/ast.rs +0 -0
  95. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/src/lexer.rs +0 -0
  96. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/src/lib.rs +0 -0
  97. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/src/parser.rs +0 -0
  98. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/src/util.rs +0 -0
  99. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/parser/tests/lexer.rs +0 -0
  100. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/inference.rs +0 -0
  101. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/lib.rs +0 -0
  102. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/main.rs +0 -0
  103. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/parse_timer.rs +0 -0
  104. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/parser.rs +0 -0
  105. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/py/ast.rs +0 -0
  106. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/py/emit.rs +0 -0
  107. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/py/mod.rs +0 -0
  108. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/py/util.rs +0 -0
  109. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/resolve_scopes.rs +0 -0
  110. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/types.rs +0 -0
  111. {koatl-0.1.30 → koatl-0.1.31}/koatl-core/src/util.rs +0 -0
  112. {koatl-0.1.30 → koatl-0.1.31}/pyproject.toml +0 -0
  113. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/__init__.py +0 -0
  114. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/__main__.py +0 -0
  115. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/cli.py +0 -0
  116. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/notebook/__init__.py +0 -0
  117. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/notebook/magic.py +0 -0
  118. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/__init__.tl +0 -0
  119. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/functional/async.tl +0 -0
  120. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/functional/async_util.py +0 -0
  121. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/functional/list.tl +0 -0
  122. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/io.tl +0 -0
  123. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/prelude/iterable.tl +0 -0
  124. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/runtime/classes.py +0 -0
  125. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/runtime/helpers.py +0 -0
  126. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/runtime/meta_finder.py +0 -0
  127. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/runtime/record.py +0 -0
  128. {koatl-0.1.30 → koatl-0.1.31}/python/koatl/runtime/virtual.py +0 -0
@@ -99,7 +99,7 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
99
99
 
100
100
  [[package]]
101
101
  name = "koatl"
102
- version = "0.1.30"
102
+ version = "0.1.31"
103
103
  dependencies = [
104
104
  "ariadne",
105
105
  "koatl-core",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: koatl
3
- Version: 0.1.30
3
+ Version: 0.1.31
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "koatl"
3
- version = "0.1.30"
3
+ version = "0.1.31"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
@@ -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
- __init__ = (self, *args, **kwargs) => raise ValueError(
47
- "Identity should not be instantiated directly. "
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
- # if the object is already created.
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("do"),
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__},
@@ -0,0 +1,9 @@
1
+ import util.assert_eq
2
+
3
+ g = () =>
4
+ @Env.item("c")
5
+
6
+ f = () =>
7
+ @Env.item("a"), @Env.item("b"), @g()
8
+
9
+ assert_eq(f().run({ a: 1, b: 2, c: 3 }), (1, 2, 3))
@@ -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
- if memo_captures.is_do {
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
- __init__ = (self, *args, **kwargs) => raise ValueError(
47
- "Identity should not be instantiated directly. "
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
- # if the object is already created.
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("do"),
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