koatl 0.1.28__tar.gz → 0.1.30__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 (129) hide show
  1. {koatl-0.1.28 → koatl-0.1.30}/Cargo.lock +1 -1
  2. {koatl-0.1.28 → koatl-0.1.30}/PKG-INFO +1 -1
  3. {koatl-0.1.28 → koatl-0.1.30}/koatl/Cargo.toml +1 -1
  4. koatl-0.1.30/koatl/python/koatl/prelude/__init__.tl +4 -0
  5. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/prelude/functional/algebra.tl +28 -7
  6. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/prelude/functional/async.tl +9 -3
  7. koatl-0.1.30/koatl/python/koatl/prelude/functional/list.tl +29 -0
  8. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/prelude/functional/memo.tl +1 -1
  9. {koatl-0.1.28 → koatl-0.1.30/koatl}/python/koatl/prelude/functional/reader.tl +2 -3
  10. {koatl-0.1.28 → koatl-0.1.30/koatl}/python/koatl/prelude/functional/result.tl +31 -11
  11. koatl-0.1.30/koatl/python/koatl/prelude/io.tl +5 -0
  12. koatl-0.1.30/koatl/python/koatl/prelude/iterable.tl +194 -0
  13. {koatl-0.1.28 → koatl-0.1.30/koatl}/python/koatl/runtime/__init__.py +1 -0
  14. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/runtime/helpers.py +11 -7
  15. {koatl-0.1.28 → koatl-0.1.30/koatl}/python/koatl/runtime/record.py +7 -6
  16. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/runtime/virtual.py +3 -3
  17. {koatl-0.1.28 → koatl-0.1.30}/koatl/src/emit_py.rs +17 -13
  18. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/prelude/iterables.tl +10 -0
  19. koatl-0.1.30/koatl/tests/e2e/prelude/list.tl +47 -0
  20. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/prelude/memo.tl +1 -1
  21. koatl-0.1.30/koatl/tests/e2e/prelude/result.tl +99 -0
  22. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/parser/src/parser.rs +15 -2
  23. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/src/resolve_scopes.rs +4 -0
  24. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/src/transform.rs +2 -1
  25. koatl-0.1.30/python/koatl/prelude/__init__.tl +4 -0
  26. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/prelude/functional/algebra.tl +28 -7
  27. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/prelude/functional/async.tl +9 -3
  28. koatl-0.1.30/python/koatl/prelude/functional/list.tl +29 -0
  29. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/prelude/functional/memo.tl +1 -1
  30. {koatl-0.1.28/koatl → koatl-0.1.30}/python/koatl/prelude/functional/reader.tl +2 -3
  31. {koatl-0.1.28/koatl → koatl-0.1.30}/python/koatl/prelude/functional/result.tl +31 -11
  32. koatl-0.1.30/python/koatl/prelude/io.tl +5 -0
  33. koatl-0.1.30/python/koatl/prelude/iterable.tl +194 -0
  34. {koatl-0.1.28/koatl → koatl-0.1.30}/python/koatl/runtime/__init__.py +1 -0
  35. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/runtime/helpers.py +11 -7
  36. {koatl-0.1.28/koatl → koatl-0.1.30}/python/koatl/runtime/record.py +7 -6
  37. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/runtime/virtual.py +3 -3
  38. koatl-0.1.28/koatl/python/koatl/prelude/__init__.tl +0 -2
  39. koatl-0.1.28/koatl/python/koatl/prelude/functional/list.tl +0 -25
  40. koatl-0.1.28/koatl/python/koatl/prelude/iterable.tl +0 -79
  41. koatl-0.1.28/koatl/tests/e2e/prelude/list.tl +0 -18
  42. koatl-0.1.28/koatl/tests/e2e/prelude/result.tl +0 -29
  43. koatl-0.1.28/python/koatl/prelude/__init__.tl +0 -2
  44. koatl-0.1.28/python/koatl/prelude/functional/list.tl +0 -25
  45. koatl-0.1.28/python/koatl/prelude/iterable.tl +0 -79
  46. {koatl-0.1.28 → koatl-0.1.30}/Cargo.toml +0 -0
  47. {koatl-0.1.28 → koatl-0.1.30}/README.md +0 -0
  48. {koatl-0.1.28 → koatl-0.1.30}/koatl/.github/workflows/CI.yml +0 -0
  49. {koatl-0.1.28 → koatl-0.1.30}/koatl/.gitignore +0 -0
  50. {koatl-0.1.28 → koatl-0.1.30}/koatl/LICENSE +0 -0
  51. {koatl-0.1.28 → koatl-0.1.30}/koatl/README.md +0 -0
  52. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/__init__.py +0 -0
  53. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/__main__.py +0 -0
  54. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/cli.py +0 -0
  55. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/notebook/__init__.py +0 -0
  56. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/notebook/magic.py +0 -0
  57. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/prelude/functional/__init__.tl +0 -0
  58. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/prelude/functional/async_util.py +0 -0
  59. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/runtime/classes.py +0 -0
  60. {koatl-0.1.28 → koatl-0.1.30}/koatl/python/koatl/runtime/meta_finder.py +0 -0
  61. {koatl-0.1.28 → koatl-0.1.30}/koatl/requirements.txt +0 -0
  62. {koatl-0.1.28 → koatl-0.1.30}/koatl/src/lib.rs +0 -0
  63. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/coal.tl +0 -0
  64. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/containers.tl +0 -0
  65. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/decorators.tl +0 -0
  66. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/destructure-for-and-fn.tl +0 -0
  67. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/destructure.tl +0 -0
  68. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/escape_ident.tl +0 -0
  69. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/fstr.tl +0 -0
  70. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/functions.tl +0 -0
  71. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/generator.tl +0 -0
  72. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/if_expr.tl +0 -0
  73. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/imports.tl +0 -0
  74. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/loops.tl +0 -0
  75. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/match.tl +0 -0
  76. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/nary-list.tl +0 -0
  77. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/placeholder.tl +0 -0
  78. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/precedence.tl +0 -0
  79. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/record.tl +0 -0
  80. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/scopes.tl +0 -0
  81. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/semantic_whitespace.tl +0 -0
  82. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/base/short_circuit.tl +0 -0
  83. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/destructure.tl +0 -0
  84. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/prelude/async.tl +0 -0
  85. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/prelude/aug_assign.tl +0 -0
  86. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/prelude/reader.tl +0 -0
  87. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/prelude/slice.tl +0 -0
  88. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/prelude/try.tl +0 -0
  89. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/prelude/virtual.tl +0 -0
  90. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/util/__init__.py +0 -0
  91. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/util/module0.tl +0 -0
  92. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/util/module1.tl +0 -0
  93. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/e2e/util/module2.tl +0 -0
  94. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/parse/arith.tl +0 -0
  95. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/parse/assign.tl +0 -0
  96. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/parse/block-comments.tl +0 -0
  97. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/parse/deco.tl +0 -0
  98. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/parse/func.tl +0 -0
  99. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/parse/matches.tl +0 -0
  100. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/test_e2e.py +0 -0
  101. {koatl-0.1.28 → koatl-0.1.30}/koatl/tests/test_parse.py +0 -0
  102. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/Cargo.toml +0 -0
  103. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/parser/Cargo.toml +0 -0
  104. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/parser/src/ast.rs +0 -0
  105. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/parser/src/lexer.rs +0 -0
  106. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/parser/src/lib.rs +0 -0
  107. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/parser/src/util.rs +0 -0
  108. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/parser/tests/lexer.rs +0 -0
  109. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/src/inference.rs +0 -0
  110. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/src/lib.rs +0 -0
  111. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/src/main.rs +0 -0
  112. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/src/parse_timer.rs +0 -0
  113. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/src/parser.rs +0 -0
  114. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/src/py/ast.rs +0 -0
  115. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/src/py/emit.rs +0 -0
  116. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/src/py/mod.rs +0 -0
  117. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/src/py/util.rs +0 -0
  118. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/src/types.rs +0 -0
  119. {koatl-0.1.28 → koatl-0.1.30}/koatl-core/src/util.rs +0 -0
  120. {koatl-0.1.28 → koatl-0.1.30}/pyproject.toml +0 -0
  121. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/__init__.py +0 -0
  122. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/__main__.py +0 -0
  123. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/cli.py +0 -0
  124. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/notebook/__init__.py +0 -0
  125. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/notebook/magic.py +0 -0
  126. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/prelude/functional/__init__.tl +0 -0
  127. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/prelude/functional/async_util.py +0 -0
  128. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/runtime/classes.py +0 -0
  129. {koatl-0.1.28 → koatl-0.1.30}/python/koatl/runtime/meta_finder.py +0 -0
@@ -99,7 +99,7 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
99
99
 
100
100
  [[package]]
101
101
  name = "koatl"
102
- version = "0.1.28"
102
+ version = "0.1.30"
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.28
3
+ Version: 0.1.30
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.28"
3
+ version = "0.1.30"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
@@ -0,0 +1,4 @@
1
+ export import .functional.*
2
+
3
+ export import .iterable.Iterable
4
+ export import .io
@@ -3,7 +3,8 @@ export Monad = class(Trait):
3
3
  Abstract base class for monads.
4
4
 
5
5
  Subclasses must implement `bind` and `pure`.
6
- Automatically provides standard monadic operations: bind_once, map, and apply.
6
+ Automatically provides (likely suboptimal) standard
7
+ functor operations: map, apply, lift.
7
8
  """
8
9
  __slots__ = ()
9
10
 
@@ -13,18 +14,20 @@ export Monad = class(Trait):
13
14
 
14
15
  # Automatically generated implementations.
15
16
  map = (self, f) => self.bind(x => self.pure(f(x)))
16
- apply = (self, f) => self.bind(x => f.map(fn => fn(x)))
17
+ apply = (self, mf) => self.bind(x => mf.map(f => f(x)))
17
18
 
18
19
 
19
20
  export MonadOnce = class(Monad, Trait):
20
21
  """
21
- Abstract base class for single-use monads.
22
+ Abstract base class for monads that don't require branching.
22
23
 
23
- A MonadOnce can only be bound once, which is suitable for
24
+ A MonadOnce only calls `f` in `bind_once` once, which is suitable for
24
25
  use with Python generators that cannot be cloned or reused.
25
26
 
26
27
  Subclasses must implement `bind_once` and `pure`.
27
- Automatically provides (likely suboptimal) standard monadic operations: bind, map, and apply.
28
+
29
+ Subclasses may also implement `bind_gen` for more efficient
30
+ handling of do-blocks.
28
31
  """
29
32
  __slots__ = ()
30
33
 
@@ -37,5 +40,23 @@ export MonadOnce = class(Monad, Trait):
37
40
 
38
41
  # Automatically generated implementations.
39
42
  bind = (self, f) => self.bind_once(f)
40
- map = (self, f) => self.bind(x => self.pure(f(x)))
41
- apply = (self, f) => self.bind(x => f.map(fn => fn(x)))
43
+
44
+
45
+ export Identity = class(MonadOnce):
46
+ __init__ = (self, *args, **kwargs) => raise ValueError(
47
+ "Identity should not be instantiated directly. "
48
+ )
49
+
50
+ bind_once = (self, f) => f(self)
51
+
52
+ pure = staticmethod& x => x
53
+
54
+
55
+ export Traversable = class(Trait):
56
+ """
57
+ Abstract base class for traversable data structures.
58
+ Subclasses must implement `traverse`.
59
+ """
60
+ __slots__ = ()
61
+
62
+ traverse = Abstract& (self, f) => None
@@ -24,7 +24,7 @@ export Async = class(MonadOnce):
24
24
  if hasattr(result, "__await__"):
25
25
  return yield from result.__await__()
26
26
 
27
- return result
27
+ result
28
28
 
29
29
  bind_gen = (self, gen) => Async.from_generator_fn& () =>
30
30
  try:
@@ -39,5 +39,11 @@ export Async = class(MonadOnce):
39
39
 
40
40
  sleep = staticmethod& x => Async(asyncio.sleep(x))
41
41
 
42
- # TODO: Why is asyncio.gather eager?
43
- gather = staticmethod& (*args) => Async.from_generator_fn& () => yield from asyncio.gather(*args)
42
+ # These execute immediately, so we need to wrap them in a generator
43
+ # (otherwise, there might not be an event loop running)
44
+
45
+ gather = staticmethod& (*args) => Async.from_generator_fn& () =>
46
+ yield from asyncio.gather(*args)
47
+
48
+ from_sync = staticmethod& (f, *args, **kwargs) => Async.from_generator_fn& () =>
49
+ yield from asyncio.get_running_loop().run_in_executor(None, () => f(*args, **kwargs))
@@ -0,0 +1,29 @@
1
+ import itertools
2
+
3
+ import .Monad
4
+
5
+ export List = class(Monad):
6
+ __new__ = (cls, *args, **kwargs) => raise ValueError(
7
+ "List cannot be instantiated directly. "
8
+ "Use [] or list()."
9
+ )
10
+
11
+ bind_once = (self, f) =>
12
+ raise NotImplementedError(
13
+ "List may not be bound using @ "
14
+ "due to generator limitations. "
15
+ "Wrap in Ok to use the Result monad."
16
+ )
17
+
18
+ bind = (self, f) => List(self.flat_map(f))
19
+
20
+ pure = staticmethod& x => [x]
21
+
22
+ len = TraitProperty& self => len(self)
23
+
24
+
25
+ for name, method in List.__dict__:
26
+ if name.startswith("_"):
27
+ continue
28
+
29
+ ExtensionMethod(list, name)& method
@@ -64,7 +64,7 @@ export Memo = class(MonadOnce):
64
64
  bind_once = (self, f) => Memo(ctx =>
65
65
  let value = f(self.f(ctx))
66
66
  if value matches Memo():
67
- value = value.f(ctx)
67
+ return value.f(ctx)
68
68
  value
69
69
  )
70
70
 
@@ -11,9 +11,8 @@ export Reader = class(MonadOnce):
11
11
  bind_once = (self, f) => Reader& ctx =>
12
12
  let v = f(self.fn(ctx))
13
13
  if v matches Reader():
14
- v.fn(ctx)
15
- else:
16
- v
14
+ return v.fn(ctx)
15
+ v
17
16
 
18
17
  bind_gen = (self, gen) => Reader& ctx =>
19
18
  try:
@@ -1,5 +1,5 @@
1
1
  import functools.wraps
2
- import .algebra.MonadOnce
2
+ import .algebra.(Identity, MonadOnce)
3
3
 
4
4
  export Result = class(MonadOnce):
5
5
  __slots__ = ()
@@ -28,9 +28,7 @@ export Result = class(MonadOnce):
28
28
  if not self.ok:
29
29
  return self
30
30
 
31
- let v = Result(f(self.value))
32
-
33
- bind = bind_once
31
+ f(self.value)
34
32
 
35
33
  bind_gen = (self, gen) =>
36
34
  self = Result(self)
@@ -41,7 +39,7 @@ export Result = class(MonadOnce):
41
39
 
42
40
  self = Result(gen.send(self.value))
43
41
  except StopIteration(value=value):
44
- return Result.pure(value)
42
+ return Result(value)
45
43
 
46
44
  pure = staticmethod& x => Ok(x)
47
45
 
@@ -74,6 +72,29 @@ export Result = class(MonadOnce):
74
72
  return Err(f())
75
73
  return self
76
74
 
75
+ T = class(MonadOnce):
76
+ __slots__ = ("value",)
77
+ __init__ = (self, value) =>
78
+ if not hasattr(value, "bind_once"):
79
+ raise TypeError("Expected a MonadOnce value")
80
+
81
+ self.value = value
82
+
83
+ __repr__ = self => f"Result.T({repr(self.value)})"
84
+
85
+ pure = (self, x) => self.value.pure(Result.pure(x))
86
+
87
+ bind_once = (self, f) =>
88
+ self.value.bind_once(x =>
89
+ x = Result(x)
90
+
91
+ if not x.ok:
92
+ return self.value.pure(x)
93
+
94
+ f(x.value)
95
+ )
96
+
97
+
77
98
  export Ok = class(Result):
78
99
  __slots__ = ("value",)
79
100
  ok = True
@@ -95,11 +116,12 @@ export Ok = class(Result):
95
116
  unwrap = self => self.value
96
117
  coalesce = (self, f) => self.value
97
118
 
119
+
98
120
  export Err = class(Result):
99
121
  __slots__ = ("value",)
100
122
  ok = False
101
- __new__ = (cls, value) => object.__new__(cls)
102
- __init__ = (self, value) =>
123
+ __new__ = (cls, value=None) => object.__new__(cls)
124
+ __init__ = (self, value=None) =>
103
125
  if hasattr(self, "value"):
104
126
  return None
105
127
  self.value = value
@@ -109,7 +131,7 @@ export Err = class(Result):
109
131
  return False
110
132
  return self.value == other.value
111
133
 
112
- __repr__ = self => f"Err({repr(self.value)})"
134
+ __repr__ = self => f"Err({self.value === None then "" else repr(self.value)})"
113
135
  unwrap = self =>
114
136
  if self.value matches BaseException():
115
137
  raise self.value
@@ -123,11 +145,9 @@ ExtensionProperty(object, "ok")& _ => True
123
145
 
124
146
  ExtensionProperty(object, "result")& Result
125
147
 
126
- # Enables @ operator for bare objects.
127
- ExtensionMethod(object, "bind_gen")& Result.bind_gen
128
-
129
148
  __tl__.Ok = Ok
130
149
  __tl__.Err = Err
150
+ __tl__.Result = Result
131
151
 
132
152
  __tl__.op_coal = (x, f) =>
133
153
  if x matches Result():
@@ -0,0 +1,5 @@
1
+ export read = (filename) =>
2
+ let desc = open(filename, "r")
3
+ let data = desc.read()
4
+ desc.close()
5
+ data
@@ -0,0 +1,194 @@
1
+ import itertools
2
+ import builtins
3
+ import .functional.Traversable
4
+ import .functional.(Ok, Err)
5
+
6
+ export Iterable = ExtensionTrait& class(Traversable, Trait):
7
+ iter = Abstract
8
+
9
+ skip = (self, n) =>
10
+ let it = self.iter
11
+ for _ in ..n:
12
+ next(it, None)
13
+ it
14
+
15
+ take = (self, n) =>
16
+ let impl = () =>
17
+ let it = self.iter
18
+ for _ in ..n:
19
+ try:
20
+ yield next(it)
21
+ except StopIteration(value=value):
22
+ return value
23
+
24
+ impl.__name__ = f"take"
25
+ impl.__qualname__ = f"Iterable.take"
26
+
27
+ impl()
28
+
29
+ take_while = (self, f) =>
30
+ let impl = () =>
31
+ for i in self.iter:
32
+ if not f(i):
33
+ return None
34
+ yield i
35
+
36
+ impl.__name__ = f"take_while"
37
+ impl.__qualname__ = f"Iterable.take_while"
38
+
39
+ impl()
40
+
41
+ chain = (self, *others) =>
42
+ itertools.chain(self.iter, *others.map($.iter))
43
+
44
+ zip = (self, *others) =>
45
+ zip(self.iter, *others.map($.iter))
46
+
47
+ enumerate = (self, start=0) =>
48
+ enumerate(self.iter, start)
49
+
50
+ map = (self, f) => builtins.map(f, self.iter)
51
+
52
+ filter = (self, f) => builtins.filter(f, self.iter)
53
+
54
+ flat_map = (self, f) => itertools.chain.from_iterable(self.map(f))
55
+
56
+ reverse = self => reversed(list(self.iter))
57
+
58
+ sort = (self, key=None, reverse=False) =>
59
+ sorted(self.iter, key=key, reverse=reverse)
60
+
61
+ copy = self => itertools.tee(self.iter)
62
+
63
+
64
+ count = self => len(self.iter)
65
+
66
+ fold = (self, init, f) =>
67
+ let acc = init
68
+ for i in self:
69
+ acc = f(acc, i)
70
+ acc
71
+
72
+ associate = (self, f) =>
73
+ let acc = dict() # Don't use Record here.
74
+ for i in self:
75
+ let value = f(i)
76
+ acc[i] = value
77
+ acc
78
+
79
+ group_by = (self, f) =>
80
+ let acc = dict()
81
+ for i in self:
82
+ let key = f(i)
83
+ if not acc.__contains__(key):
84
+ acc[key] = []
85
+ acc[key].append(i)
86
+ acc
87
+
88
+ count_by = (self, f) =>
89
+ let acc = dict()
90
+ for i in self:
91
+ let key = f(i)
92
+ if not acc.__contains__(key):
93
+ acc[key] = 0
94
+ acc[key] += 1
95
+ acc
96
+
97
+ all = (self, f) =>
98
+ for i in self:
99
+ if not f(i):
100
+ return False
101
+ return True
102
+
103
+ any = (self, f) =>
104
+ for i in self:
105
+ if f(i):
106
+ return True
107
+ return False
108
+
109
+ find = (self, f) =>
110
+ for i in self:
111
+ if f(i):
112
+ return Ok(i)
113
+ return Err()
114
+
115
+ first = self =>
116
+ (try next(self.iter)).map_err(_ => None)
117
+
118
+ last = (self, f) =>
119
+ let result = Err()
120
+ for i in self:
121
+ if f(i):
122
+ result = Ok(i)
123
+ return result
124
+
125
+ at = (self, index) =>
126
+ for i in self:
127
+ if i == index:
128
+ return Ok(i)
129
+ return Err()
130
+
131
+ sum = self =>
132
+ let acc = 0
133
+ for i in self:
134
+ acc = acc + i
135
+ acc
136
+
137
+ for_each = (self, f) =>
138
+ for i in self:
139
+ f(i)
140
+
141
+ list = self =>
142
+ list(self.iter)
143
+
144
+ dict = self =>
145
+ dict(self.iter)
146
+
147
+ record = self =>
148
+ Record(self.iter)
149
+
150
+ traverse = (self, f=None) =>
151
+ import .functional.(Async, Result)
152
+
153
+ if f === None:
154
+ f = x => x
155
+
156
+ let it = self.iter
157
+ let v
158
+ try:
159
+ v = next(it)
160
+ except StopIteration():
161
+ return []
162
+
163
+ let m = f(v)
164
+
165
+ # TODO: can special case traversals be generalized?
166
+
167
+ # The Result traversal of a list is short-circuiting.
168
+ # Fall back to Result semantics for bare types.
169
+ if m matches Result() or not hasattr(m, "apply"):
170
+ if not m.ok:
171
+ return m
172
+
173
+ let acc = [m]
174
+ for v in it:
175
+ let fv = f(v)
176
+ if not fv.ok:
177
+ return fv
178
+ acc.append(fv)
179
+ return acc
180
+
181
+ # The Async traversal of a list is gather.
182
+ if m matches Async():
183
+ let acc = [m]
184
+ for v in it:
185
+ acc.append(f(v))
186
+ return Async.gather(*acc)
187
+
188
+ # Fall back to default applicative semantics.
189
+ let acc = m.map([$])
190
+ for v in it:
191
+ m = f(v)
192
+ # acc = liftA2((list, value) => [*list, value])(acc, f(v))
193
+ acc = acc.apply(m.map(x => acc => [*acc, x]))
194
+ acc
@@ -48,6 +48,7 @@ __tl__ = SimpleNamespace(
48
48
  op_coal=("??"),
49
49
  Ok=dummy("try-expr"),
50
50
  Err=dummy("try-expr"),
51
+ Result=dummy("do"),
51
52
  **{name: helpers.__dict__[name] for name in helpers.__all__},
52
53
  **{name: record.__dict__[name] for name in record.__all__},
53
54
  **{name: virtual.__dict__[name] for name in virtual.__all__},
@@ -1,6 +1,5 @@
1
1
  from functools import wraps
2
2
  from .virtual import vget
3
- from .record import Record
4
3
 
5
4
 
6
5
  __all__ = ["MatchError"]
@@ -56,11 +55,10 @@ def do(f):
56
55
  try:
57
56
  m = gen.send(None)
58
57
  except StopIteration as e:
59
- if not hasattr(e.value, "bind_once"):
60
- raise ValueError(
61
- "This do-block returned a bare value before it could infer the monadic type. Wrap the value in pure()."
62
- ) from None
63
- return e.value
58
+ raise ValueError(
59
+ "Returning before `@` is not allowed. "
60
+ "Use `return @MonadType.pure(value)` instead."
61
+ ) from None
64
62
 
65
63
  def recurse(v):
66
64
  nonlocal m
@@ -76,7 +74,13 @@ def do(f):
76
74
 
77
75
  bind_gen = vget(m, "bind_gen")
78
76
  except AttributeError:
79
- return vget(m, "bind_once")(recurse)
77
+ try:
78
+ return vget(m, "bind_once")(recurse)
79
+ except AttributeError:
80
+ # Fallback to Result if m is not a monadic type
81
+ from . import __tl__
82
+
83
+ bind_gen = __tl__.Result(m).bind_gen
80
84
 
81
85
  return bind_gen(gen)
82
86
 
@@ -1,4 +1,5 @@
1
1
  import re
2
+ from .virtual import vget
2
3
 
3
4
  __all__ = ["Record"]
4
5
 
@@ -11,9 +12,7 @@ class Record(dict):
11
12
  try:
12
13
  return self[name]
13
14
  except KeyError:
14
- raise AttributeError(
15
- f"'{type(self).__name__}' object has no attribute '{name}'"
16
- ) from None
15
+ return super().__getattr__(name)
17
16
 
18
17
  def _repr_with_visited(self, visited):
19
18
  # Handle cycles by checking if this object is already being processed
@@ -46,9 +45,11 @@ class Record(dict):
46
45
  visited.remove(obj_id)
47
46
 
48
47
  def _format_key(self, key):
49
- if isinstance(key, str) and self._is_identifier(key):
50
- # If key is an identifier, drop the quotes
51
- return key
48
+ if isinstance(key, str):
49
+ if self._is_identifier(key):
50
+ # If key is an identifier, drop the quotes
51
+ return key
52
+ return f'"{key}"'
52
53
 
53
54
  elif isinstance(key, (int, float, bool, type(None))):
54
55
  # If key is a literal like 0, 1, True, False, None, use repr
@@ -14,12 +14,12 @@ def vget(obj, name, ignore_traits=False):
14
14
  start = obj.start if obj.start is not None else 0
15
15
  step = obj.step if obj.step is not None else 1
16
16
  if obj.stop is None:
17
- return count(start, step)
17
+ return iter(count(start, step))
18
18
  else:
19
- return range(start, obj.stop, step)
19
+ return iter(range(start, obj.stop, step))
20
20
 
21
21
  try:
22
- return obj.items()
22
+ return iter(obj.items())
23
23
  except AttributeError:
24
24
  pass
25
25
 
@@ -496,19 +496,23 @@ trait PyLiteralExt<'src> {
496
496
  impl<'src> PyLiteralExt<'src> for PyLiteral<'src> {
497
497
  fn emit_py<'py>(&self, ctx: &PyCtx<'py, 'src>, span: &Span) -> PyTlResult<PyObject> {
498
498
  Ok(match self {
499
- PyLiteral::Num(num) => match num.parse::<i128>() {
500
- Ok(i) => ctx.ast_node("Constant", (i,), span)?,
501
- Err(_) => match num.parse::<f64>() {
502
- Ok(f) => ctx.ast_node("Constant", (f,), span)?,
503
- Err(_) => {
504
- return Err(PyTlErr {
505
- message: format!("Invalid number literal: {}", num),
506
- py_err: None,
507
- span: Some(*span),
508
- })
509
- }
510
- },
511
- },
499
+ PyLiteral::Num(num) => {
500
+ let num = num.replace('_', "");
501
+
502
+ match num.parse::<i128>() {
503
+ Ok(i) => ctx.ast_node("Constant", (i,), span)?,
504
+ Err(_) => match num.parse::<f64>() {
505
+ Ok(f) => ctx.ast_node("Constant", (f,), span)?,
506
+ Err(_) => {
507
+ return Err(PyTlErr {
508
+ message: format!("Invalid number literal: {}", num),
509
+ py_err: None,
510
+ span: Some(*span),
511
+ });
512
+ }
513
+ },
514
+ }
515
+ }
512
516
  PyLiteral::Bool(b) => ctx.ast_node("Constant", (b,), span)?,
513
517
  PyLiteral::Str(s) => ctx.ast_node("Constant", (s,), span)?,
514
518
  PyLiteral::None => ctx.ast_node("Constant", (ctx.py.None(),), span)?,
@@ -18,4 +18,14 @@ assert_eq(
18
18
  assert_eq(
19
19
  {1: 2}.map([k, v] => [v, k]).record(),
20
20
  {2: 1}
21
+ )
22
+
23
+ assert_eq(
24
+ (..3).associate($ + 2)
25
+ {0: 2, 1: 3, 2: 4}
26
+ )
27
+
28
+ assert_eq(
29
+ (..6).group_by($ < 2)
30
+ {False: [2, 3, 4, 5], True: [0, 1]}
21
31
  )
@@ -0,0 +1,47 @@
1
+ import util.assert_eq
2
+ import time
3
+
4
+ # Async traversal must be concurrent.
5
+
6
+ now = time.time()
7
+
8
+ assert_eq(
9
+ (..10).traverse(x =>
10
+ @Async.sleep(0.1)
11
+ x
12
+ ).run()
13
+ (..10).list()
14
+ )
15
+
16
+ assert_eq(time.time() - now < 0.3, True)
17
+
18
+ # Result traversal must short-circuit.
19
+
20
+ # Bare Result traversal should not be wrapped in Ok.
21
+
22
+ assert_eq(
23
+ [1, 2, 3].traverse(x => x + 1)
24
+ [2, 3, 4]
25
+ )
26
+
27
+ counts = 0
28
+ assert_eq(
29
+ (..20).traverse(x =>
30
+ counts += 1
31
+ if x == 10:
32
+ return Err()
33
+ Ok(x)
34
+ )
35
+ Err()
36
+ )
37
+ assert_eq(counts, 11)
38
+
39
+ counts = 0
40
+ assert_eq(
41
+ (..10).traverse(x =>
42
+ counts += 1
43
+ Ok(x)
44
+ )
45
+ (..10).map(Ok).list()
46
+ )
47
+ assert_eq(counts, 10)
@@ -4,7 +4,7 @@ counts = 0
4
4
 
5
5
  fib = Memo.fn& x =>
6
6
  if x < 2:
7
- return Memo.pure(1)
7
+ return @Memo.pure(1)
8
8
 
9
9
  counts = counts + 1
10
10