Skip to content

Commit b5aefb1

Browse files
committed
let: new alternate haskelly syntax let[((k0, v0), ...) in body] and let[body, where((k0, v0), ...)]; remove let0 (now subsumed by the new syntax)
1 parent f8082fb commit b5aefb1

File tree

5 files changed

+293
-115
lines changed

5 files changed

+293
-115
lines changed

unpythonic/syntax/__init__.py

Lines changed: 28 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919
quicklambda as _quicklambda, f, _
2020
from .letdo import do as _do, do0 as _do0, local, \
2121
let as _let, letseq as _letseq, letrec as _letrec, \
22-
let0 as _let0, letseq0 as _letseq0, letrec0 as _letrec0, \
2322
dlet as _dlet, dletseq as _dletseq, dletrec as _dletrec, \
2423
blet as _blet, bletseq as _bletseq, bletrec as _bletrec
2524
from .letsyntax import let_syntax_expr, let_syntax_block, block, expr
2625
from .prefix import prefix as _prefix
2726
from .tailtools import autoreturn as _autoreturn, tco as _tco, \
2827
continuations as _continuations, with_cc
2928

29+
from .util import UnexpandedLetView
3030
from ..dynassign import dyn, make_dynvar
3131

3232
from macropy.core.macros import Macros
@@ -160,16 +160,26 @@ def let(tree, args, *, gen_sym, **kw):
160160
161161
Usage::
162162
163-
let(bindings)[body]
164-
let(bindings)[[body0, ...]]
163+
let((k0, v0), ...)[body]
164+
let((k0, v0), ...)[[body0, ...]]
165165
166-
where ``bindings`` is a comma-separated sequence of pairs ``(name, value)``
167-
and ``body`` is an expression. The names bound by ``let`` are local;
166+
where ``body`` is an expression. The names bound by ``let`` are local;
168167
they are available in ``body``, and do not exist outside ``body``.
169168
170-
For a body with multiple expressions, use an extra set of brackets.
171-
This inserts a ``do``. Only the outermost extra brackets are interpreted
172-
specially; all others in the bodies are interpreted as usual, as lists.
169+
Alternative haskelly syntax is also available::
170+
171+
let[((k0, v0), ...) in body]
172+
let[((k0, v0), ...) in [body0, ...]]
173+
let[body, where((k0, v0), ...)]
174+
let[[body0, ...], where((k0, v0), ...)]
175+
176+
For a body with multiple expressions, use an extra set of brackets,
177+
as shown above. This inserts a ``do``. Only the outermost extra brackets
178+
are interpreted specially; all others in the bodies are interpreted
179+
as usual, as lists.
180+
181+
Note that in the haskelly syntax, the extra brackets for a multi-expression
182+
body should enclose only the ``body`` part.
173183
174184
Each ``name`` in the same ``let`` must be unique.
175185
@@ -199,7 +209,7 @@ def let(tree, args, *, gen_sym, **kw):
199209
is applied first to ``[body0, ...]``, and the result becomes ``body``.
200210
"""
201211
with dyn.let(gen_sym=gen_sym):
202-
return _let(bindings=args, body=tree)
212+
return _destructure_and_apply_let(tree, args, _let)
203213

204214
@macros.expr
205215
def letseq(tree, args, *, gen_sym, **kw):
@@ -211,7 +221,7 @@ def letseq(tree, args, *, gen_sym, **kw):
211221
Expands to nested ``let`` expressions.
212222
"""
213223
with dyn.let(gen_sym=gen_sym):
214-
return _letseq(bindings=args, body=tree)
224+
return _destructure_and_apply_let(tree, args, _letseq)
215225

216226
@macros.expr
217227
def letrec(tree, args, *, gen_sym, **kw):
@@ -228,55 +238,14 @@ def letrec(tree, args, *, gen_sym, **kw):
228238
This is useful for locally defining mutually recursive functions.
229239
"""
230240
with dyn.let(gen_sym=gen_sym):
231-
return _letrec(bindings=args, body=tree)
232-
233-
# -----------------------------------------------------------------------------
234-
# Inverted let, for situations where a body-first style improves readability.
235-
236-
@macros.expr
237-
def let0(tree, *, gen_sym, **kw):
238-
"""[syntax, expr] Inverted let.
239-
240-
Because sometimes it is more readable to give the body first.
241-
242-
Usage::
243-
244-
let0[body, where((k0, v0), ...)]
245-
let0[[body0, ...], where((k0, v0), ...)]
246-
247-
The ``where`` is literal.
248-
249-
Use extra brackets around the body (only) for multiple-expression mode
250-
(a.k.a. implicit ``do[]``).
251-
252-
Inspired by Haskell's ``where``; this format is also common in mathematics.
253-
"""
254-
with dyn.let(gen_sym=gen_sym):
255-
return _let0(tree)
256-
257-
@macros.expr
258-
def letseq0(tree, *, gen_sym, **kw):
259-
"""[syntax, expr] Inverted letseq.
260-
261-
Usage::
262-
263-
letseq0[body, where((k0, v0), ...)]
264-
letseq0[[body0, ...], where((k0, v0), ...)]
265-
"""
266-
with dyn.let(gen_sym=gen_sym):
267-
return _letseq0(tree)
268-
269-
@macros.expr
270-
def letrec0(tree, *, gen_sym, **kw):
271-
"""[syntax, expr] Inverted letrec.
272-
273-
Usage::
274-
275-
letrec0[body, where((k0, v0), ...)]
276-
letrec0[[body0, ...], where((k0, v0), ...)]
277-
"""
278-
with dyn.let(gen_sym=gen_sym):
279-
return _letrec0(tree)
241+
return _destructure_and_apply_let(tree, args, _letrec)
242+
243+
def _destructure_and_apply_let(tree, args, expander):
244+
if args:
245+
return expander(bindings=args, body=tree)
246+
# haskelly syntax
247+
view = UnexpandedLetView(tree) # note this gets only the part inside the brackets
248+
return expander(bindings=view.bindings, body=view.body)
280249

281250
# -----------------------------------------------------------------------------
282251
# Decorator versions, for "let over def".

unpythonic/syntax/letdo.py

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -130,34 +130,6 @@ def envwrap(tree, envname):
130130
lam.args.args = [arg(arg=envname)] # lambda e44: ...
131131
return lam
132132

133-
# -----------------------------------------------------------------------------
134-
# Inverted let, for situations where a body-first style improves readability.
135-
# let0[body, where((k0, v0), ...)]
136-
# let0[[body0, ...], where((k0, v0), ...)]
137-
def _validate_let0(tree, myname):
138-
if not (type(tree) is Tuple and len(tree.elts) == 2 and
139-
type(tree.elts[1]) is Call and \
140-
type(tree.elts[1].func) is Name and tree.elts[1].func.id == "where"):
141-
assert False, "expected {}[body, where((k0, v0), ...)]".format(myname)
142-
143-
def _let0_body(tree): # single expr, or list for implicit do
144-
return tree.elts[0]
145-
146-
def _let0_bindings(tree): # list [(k0, v0), ...]
147-
return tree.elts[1].args
148-
149-
def let0(tree):
150-
_validate_let0(tree, "let0")
151-
return let(bindings=_let0_bindings(tree), body=_let0_body(tree))
152-
153-
def letseq0(tree):
154-
_validate_let0(tree, "letseq0")
155-
return letseq(bindings=_let0_bindings(tree), body=_let0_body(tree))
156-
157-
def letrec0(tree):
158-
_validate_let0(tree, "letrec0")
159-
return letrec(bindings=_let0_bindings(tree), body=_let0_body(tree))
160-
161133
# -----------------------------------------------------------------------------
162134
# Decorator versions, for "let over def".
163135

unpythonic/syntax/prefix.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from macropy.core.quotes import macros, q, u, ast_literal
1010
from macropy.core.walkers import Walker
1111

12-
from .util import islet, isletsyntax, isdo
12+
from .util import islet, isdo, UnexpandedLetView, UnexpandedDoView
1313

1414
from ..it import flatmap, rev, uniqify
1515

@@ -20,26 +20,27 @@ def prefix(block_body):
2020
@Walker
2121
def transform(tree, *, quotelevel, set_ctx, stop, **kw):
2222
# Not tuples but syntax: leave alone the:
23-
# - bindings blocks of let, letseq, letrec, and the d*, b* variants
23+
# - binding pair "tuples" of let, letseq, letrec, their d*, b* variants,
24+
# and let_syntax, abbrev
2425
# - subscript part of an explicit do[], do0[]
2526
# but recurse inside them.
2627
#
2728
# let and do have not expanded yet when prefix runs (better that way!).
28-
if islet(tree, expanded=False) or isletsyntax(tree):
29-
# let((x, 42))[...] appears as Subscript(value=Call(...), ...);
30-
# we automatically recurse in other parts of the Subscript.
31-
# Here we only need to treat how to proceed inside the Call part.
29+
if islet(tree, expanded=False):
3230
stop()
33-
for binding in tree.args: # TODO: kwargs support for let(x=42)[...] if implemented later
31+
view = UnexpandedLetView(tree)
32+
for binding in view.bindings:
3433
if type(binding) is not Tuple:
3534
assert False, "prefix: expected a tuple in let binding position"
3635
_, value = binding.elts # leave name alone, recurse into value
3736
binding.elts[1] = transform.recurse(value, quotelevel=quotelevel)
37+
if view.body:
38+
view.body = transform.recurse(view.body, quotelevel=quotelevel)
3839
return tree
3940
elif isdo(tree, expanded=False):
4041
stop()
41-
tree.slice.value.elts = [transform.recurse(expr, quotelevel=quotelevel)
42-
for expr in tree.slice.value.elts]
42+
view = UnexpandedDoView(tree)
43+
view.body = [transform.recurse(expr, quotelevel=quotelevel) for expr in view.body]
4344
return tree
4445
# general case
4546
# macro-created nodes might not have a ctx, but we run in the first pass.

unpythonic/syntax/test/test_letdo.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
# added by unpythonic).
88

99
from ...syntax import macros, let, letseq, letrec, \
10-
let0, letseq0, letrec0, \
1110
dlet, dletseq, dletrec, \
1211
blet, bletseq, bletrec, \
1312
do, do0, local
@@ -426,22 +425,39 @@ def test14():
426425
result.append(lst)]]
427426
assert result == [[], [1]]
428427

428+
# haskelly syntax
429+
result = let[((foo, 5),
430+
(bar, 2))
431+
in foo + bar]
432+
assert result == 7
433+
434+
result = letseq[((foo, 100),
435+
(foo, 2*foo),
436+
(foo, 4*foo))
437+
in foo]
438+
assert result == 800
439+
440+
result = letrec[((evenp, lambda x: (x == 0) or oddp(x - 1)),
441+
(oddp, lambda x: (x != 0) and evenp(x - 1)))
442+
in [print("hi from letrec-in"),
443+
evenp(42)]]
444+
429445
# inverted let, for situations where a body-first style improves readability:
430-
result = let0[foo + bar,
446+
result = let[foo + bar,
431447
where((foo, 5),
432448
(bar, 2))]
433449
assert result == 7
434450

435-
result = letseq0[foo,
451+
result = letseq[foo,
436452
where((foo, 100),
437453
(foo, 2*foo),
438454
(foo, 4*foo))]
439455
assert result == 800
440456

441457
# can also use the extra bracket syntax to get an implicit do
442458
# (note the [] should then enclose the body only).
443-
result = letrec0[[print("hi from letrec0"),
444-
evenp(42)],
459+
result = letrec[[print("hi from letrec-where"),
460+
evenp(42)],
445461
where((evenp, lambda x: (x == 0) or oddp(x - 1)),
446462
(oddp, lambda x: (x != 0) and evenp(x - 1)))]
447463
assert result is True

0 commit comments

Comments
 (0)