|
6 | 6 | with monadic_do[M] as result: |
7 | 7 | [x := mx, |
8 | 8 | y := my(x), |
9 | | - M.guard(...)] in M.unit(x + y) |
| 9 | + M.guard(...), |
| 10 | + M.unit(x + y)] |
10 | 11 |
|
11 | | -Expands to:: |
| 12 | +The body is a single list literal. Each item corresponds to one line of |
| 13 | +a Haskell do-block. The **last item** is the final monadic expression |
| 14 | +(any expression of type ``M a``, matching Haskell's last-line-of-do). |
| 15 | +All **earlier items** are binds: |
12 | 16 |
|
13 | | - result = mx >> (lambda x: my(x) >> (lambda _: M.guard(...) >> (lambda _: M.unit(x + y)))) |
14 | | -
|
15 | | -The bindings list on the left of ``in`` uses the same ``:=`` / ``<<`` |
16 | | -binding syntax that ``let`` uses, parsed by ``letdoutil.canonize_bindings``. |
| 17 | +- ``name := mexpr`` — monadic bind: the unwrapped value is bound to |
| 18 | + ``name`` for subsequent lines. |
| 19 | +- ``name << mexpr`` — legacy alternative for ``:=`` (same shapes |
| 20 | + ``letdoutil`` recognizes for ``let[]``). |
| 21 | +- a bare ``mexpr`` — sequencing-only (Haskell's ``do { mx; ... }``): the |
| 22 | + result is threaded but discarded. The short-circuit behavior of the |
| 23 | + monad still applies (``Maybe(nil)``, ``Left``, empty ``List`` all |
| 24 | + cancel the rest of the chain). |
17 | 25 |
|
18 | | -- A ``name := mexpr`` entry introduces a monadic bind: the ``name`` is |
19 | | - bound to the unwrapped value for subsequent lines. |
20 | | -- A bare ``mexpr`` entry (no ``:=``) is a sequencing-only line — matches |
21 | | - Haskell do-notation's bare-expression form, used e.g. for ``guard``: |
22 | | - the result is threaded through the chain but discarded, so the whole |
23 | | - shape short-circuits for monads that do (Maybe's ``Nothing``, List's |
24 | | - empty, Either's ``Left``, etc.). |
| 26 | +Expands to a nested lambda-bind chain:: |
25 | 27 |
|
26 | | -The RHS of ``in`` is simply the final monadic expression — same |
27 | | -semantics as Haskell, where the last line of a ``do`` block is any |
28 | | -monadic value (``return (...)``, a direct constructor call, or a call |
29 | | -to a monad-producing function). No specific form required. |
30 | | -
|
31 | | -Empty bindings shorthand is supported: ``[] in M.unit(x)`` expands to |
32 | | -just ``result = M.unit(x)``. |
| 28 | + result = mx >> (lambda x: my(x) >> (lambda _: M.guard(...) >> (lambda _: M.unit(x + y)))) |
33 | 29 |
|
34 | 30 | **Placement in the xmas tree**: always the innermost ``with``. Its body |
35 | | -shape (a single ``[bindings] in final_expr`` statement) forbids |
36 | | -lexically wrapping other ``with`` blocks inside it, and outer two-pass |
37 | | -macros (``lazify``, ``continuations``, ``tco``, ``autocurry``, etc.) |
38 | | -expand inner macros between their two passes, which means they will |
39 | | -correctly see and edit the expanded bind chain. |
| 31 | +shape (a single list-literal statement) forbids lexically wrapping other |
| 32 | +``with`` blocks inside it, and outer two-pass macros (``lazify``, |
| 33 | +``continuations``, ``tco``, ``autocurry``, etc.) expand inner macros |
| 34 | +between their two passes, which means they will correctly see and edit |
| 35 | +the expanded bind chain. |
40 | 36 |
|
41 | 37 | **Always in its own nested ``with``** — unlike the other xmas-tree |
42 | 38 | macros which chain in one ``with`` for brevity, ``monadic_do[M] as result`` |
|
46 | 42 |
|
47 | 43 | __all__ = ["monadic_do"] |
48 | 44 |
|
49 | | -from ast import Compare, In, List, Name, NamedExpr, BinOp, LShift, Expr, Assign, Store, arg, expr |
| 45 | +from ast import List, Name, NamedExpr, BinOp, LShift, Expr, Assign, Store, arg, expr |
50 | 46 |
|
51 | 47 | from mcpyrate.quotes import macros, q, a, n # noqa: F401 |
52 | 48 |
|
@@ -91,47 +87,37 @@ def _monadic_do(block_body: list, monad_type: expr, result_name: str) -> list: |
91 | 87 | # Expand inner macros first (outside-in), just like `forall` and `autoref` do. |
92 | 88 | block_body = dyn._macro_expander.visit_recursively(block_body) |
93 | 89 |
|
94 | | - # Body must be exactly one statement, an Expr wrapping a Compare(In). |
| 90 | + # Body must be exactly one statement, an Expr wrapping a List literal. |
95 | 91 | if len(block_body) != 1: |
96 | 92 | raise SyntaxError( |
97 | | - f"monadic_do body must be a single statement of the form " |
98 | | - f"`[bindings] in final_expr`, got {len(block_body)} statements" |
| 93 | + f"monadic_do body must be a single list-literal statement, got {len(block_body)} statements" |
99 | 94 | ) # pragma: no cover |
100 | 95 | stmt = block_body[0] |
101 | | - if type(stmt) is not Expr: |
| 96 | + if type(stmt) is not Expr or type(stmt.value) is not List: |
102 | 97 | raise SyntaxError( |
103 | | - "monadic_do body must be a single expression statement " |
104 | | - "`[bindings] in final_expr`" |
105 | | - ) # pragma: no cover |
106 | | - compare = stmt.value |
107 | | - if not (type(compare) is Compare and |
108 | | - len(compare.ops) == 1 and |
109 | | - type(compare.ops[0]) is In): |
110 | | - raise SyntaxError( |
111 | | - "monadic_do body must have the form `[bindings] in final_expr`" |
| 98 | + "monadic_do body must be a single list literal `[bind, ..., final_expr]`" |
112 | 99 | ) # pragma: no cover |
113 | 100 |
|
114 | | - bindings_node = compare.left |
115 | | - final_expr = compare.comparators[0] |
116 | | - |
117 | | - # Bindings: must be a List literal. |
118 | | - if type(bindings_node) is not List: |
| 101 | + items = stmt.value.elts |
| 102 | + if not items: |
119 | 103 | raise SyntaxError( |
120 | | - "monadic_do bindings must be a list literal `[x := mx, ...]`" |
| 104 | + "monadic_do body list must have at least one item (the final monadic expression)" |
121 | 105 | ) # pragma: no cover |
122 | 106 |
|
123 | | - # Wrap bare expressions as `_ := expr` so they look like sequencing-only |
124 | | - # bindings to `canonize_bindings`. This mirrors Haskell's do-notation |
125 | | - # where a bare expression line is sequence-only (>>, not >>=). |
126 | | - normalized_elts = [ |
| 107 | + # Split: all but the last are binds; the last is the final monadic expression. |
| 108 | + *binding_items, final_expr = items |
| 109 | + |
| 110 | + # Normalize bare expressions in the binds as synthetic `_ := expr` so they |
| 111 | + # look like sequencing-only bindings to `canonize_bindings`. Matches Haskell's |
| 112 | + # do-notation where a bare expression line is sequence-only (>>, not >>=). |
| 113 | + normalized = [ |
127 | 114 | item if _is_binding_form(item) else NamedExpr(target=Name(id="_", ctx=Store()), value=item) |
128 | | - for item in bindings_node.elts |
| 115 | + for item in binding_items |
129 | 116 | ] |
130 | 117 |
|
131 | | - # Parse via letdoutil — accepts := and << for each binding, and []/() for the list shape |
132 | | - # (we already unpacked the outer List). |
133 | | - if normalized_elts: |
134 | | - canonical = canonize_bindings(normalized_elts) # [Tuple(elts=[Name(k), v]), ...] |
| 118 | + # Parse via letdoutil — accepts := and <<. |
| 119 | + if normalized: |
| 120 | + canonical = canonize_bindings(normalized) # [Tuple(elts=[Name(k), v]), ...] |
135 | 121 | pairs = [(t.elts[0].id, t.elts[1]) for t in canonical] |
136 | 122 | else: |
137 | 123 | pairs = [] |
|
0 commit comments