Skip to content

Commit 4625951

Browse files
committed
enh: curry macro: detect manual curry, skip injecting a curry when detected
1 parent 4921ae2 commit 4625951

4 files changed

Lines changed: 61 additions & 24 deletions

File tree

unpythonic/syntax/curry.py

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,44 @@
11
# -*- coding: utf-8 -*-
22
"""Automatic currying. Transforms both function definitions and calls."""
33

4-
from ast import Call, Lambda, FunctionDef, With, withitem
4+
from ast import Call, Lambda, FunctionDef
55
from .astcompat import AsyncFunctionDef
66

77
from macropy.core.quotes import macros, ast_literal
88
from macropy.core.hquotes import macros, hq
99
from macropy.core.walkers import Walker
1010

11-
from .util import suggest_decorator_index
12-
13-
from ..dynassign import dyn
11+
from .util import suggest_decorator_index, isx, make_isxpred, has_curry, \
12+
sort_lambda_decorators
1413

1514
# CAUTION: unpythonic.syntax.lambdatools.namedlambda depends on the exact names
1615
# "curryf" and "currycall" to detect an auto-curried expression with a final lambda.
1716
from ..fun import curry as curryf, _currycall as currycall
1817

18+
_iscurry = make_isxpred("curry")
19+
1920
def curry(block_body):
2021
@Walker
21-
def transform_call(tree, *, stop, **kw): # technically a node containing the current subtree
22+
def transform(tree, *, hascurry, set_ctx, stop, **kw):
2223
if type(tree) is Call:
23-
tree.args = [tree.func] + tree.args
24-
tree.func = hq[currycall]
24+
if has_curry(tree): # detect decorated lambda with manual curry
25+
set_ctx(hascurry=True) # the lambda inside the curry(...) is the next Lambda node we will descend into.
26+
if not isx(tree.func, _iscurry):
27+
tree.args = [tree.func] + tree.args
28+
tree.func = hq[currycall]
2529
elif type(tree) in (FunctionDef, AsyncFunctionDef):
26-
# TODO: detect there's no curry already.
27-
k = suggest_decorator_index("curry", tree.decorator_list)
28-
if k is not None:
29-
tree.decorator_list.insert(k, hq[curryf])
30-
else: # couldn't determine insert position; just plonk it at the end and hope for the best
31-
tree.decorator_list.append(hq[curryf])
30+
if not any(isx(item, _iscurry) for item in tree.decorator_list): # no manual curry already
31+
k = suggest_decorator_index("curry", tree.decorator_list)
32+
if k is not None:
33+
tree.decorator_list.insert(k, hq[curryf])
34+
else: # couldn't determine insert position; just plonk it at the end and hope for the best
35+
tree.decorator_list.append(hq[curryf])
3236
elif type(tree) is Lambda:
33-
# This inserts curry() as the innermost "decorator", and the curry
34-
# macro is meant to run last (after e.g. tco), so we're fine.
35-
# TODO: detect there's no curry already.
36-
tree = hq[curryf(ast_literal[tree])]
37-
# don't recurse on the lambda we just moved, but recurse inside it.
38-
stop()
39-
tree.args[0].body = transform_call.recurse(tree.args[0].body)
37+
if not hascurry:
38+
tree = hq[curryf(ast_literal[tree])]
39+
# don't recurse on the lambda we just moved, but recurse inside it.
40+
stop()
41+
tree.args[0].body = transform.recurse(tree.args[0].body, hascurry=False)
4042
return tree
41-
return transform_call.recurse(block_body)
43+
newbody = transform.recurse(block_body, hascurry=False)
44+
return sort_lambda_decorators(newbody)

unpythonic/syntax/tailtools.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
suggest_decorator_index
2727
from .letdoutil import isdo, islet, ExpandedLetView, ExpandedDoView
2828
from .ifexprs import aif
29-
from .letdo import let
3029

3130
from ..dynassign import dyn
3231
from ..it import uniqify

unpythonic/syntax/test/test_curry.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from ...fun import composerc as compose
88
from ...llist import cons, nil, ll
99

10+
from macropy.tracing import macros, show_expanded
11+
1012
def test():
1113
with curry:
1214
mymap = lambda f: foldr(compose(cons, f), nil)
@@ -49,4 +51,23 @@ def stuffinto(lst, x):
4951
stuffinto(lst, 5)
5052
assert lst == [1, 2, 3, 4, 5]
5153

54+
# should not insert an extra @curry even if we curry manually
55+
# (convenience, for with-currying existing code)
56+
with show_expanded:
57+
with curry:
58+
from unpythonic.fun import curry
59+
@curry
60+
def add3(a, b, c):
61+
return a + b + c
62+
assert add3(1)(2)(3) == 6
63+
64+
f = curry(lambda a, b, c: a + b + c)
65+
assert f(1)(2)(3) == 6
66+
67+
from unpythonic.tco import trampolined, jump
68+
from unpythonic.fun import withself
69+
fact = trampolined(withself(curry(lambda self, n, acc=1:
70+
acc if n == 0 else jump(self, n - 1, n * acc))))
71+
assert fact(5) == 120
72+
5273
print("All tests PASSED")

unpythonic/syntax/util.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,26 @@ def has_tco(tree, userlambdas=[]):
229229
Return value is ``True`` or ``False`` (depending on test result) if the
230230
test was applicable, and ``None`` if it was not applicable (no match on tree).
231231
"""
232+
return _has_deco(tco_decorators, tree, userlambdas)
233+
234+
def has_curry(tree, userlambdas=[]):
235+
"""Return whether a FunctionDef or a decorated lambda has curry applied.
236+
237+
userlambdas: list of ``id(some_tree)``; when detecting a lambda,
238+
only consider it if its id matches one of those in the list.
239+
240+
Return value is ``True`` or ``False`` (depending on test result) if the
241+
test was applicable, and ``None`` if it was not applicable (no match on tree).
242+
"""
243+
return _has_deco(["curry"], tree, userlambdas)
244+
245+
def _has_deco(deconames, tree, userlambdas=[]):
232246
if type(tree) in (FunctionDef, AsyncFunctionDef):
233-
return any(is_decorator(x, fname) for fname in tco_decorators for x in tree.decorator_list)
247+
return any(is_decorator(x, fname) for fname in deconames for x in tree.decorator_list)
234248
elif is_decorated_lambda(tree, mode="any"):
235249
decorator_list, thelambda = destructure_decorated_lambda(tree)
236250
if (not userlambdas) or (id(thelambda) in userlambdas):
237-
return any(is_lambda_decorator(x, fname) for fname in tco_decorators for x in decorator_list)
251+
return any(is_lambda_decorator(x, fname) for fname in deconames for x in decorator_list)
238252
return None # not applicable
239253

240254
def sort_lambda_decorators(tree):

0 commit comments

Comments
 (0)