Skip to content

Commit 79ef391

Browse files
committed
enh: lazify now works with continuations, too
1 parent 0872e27 commit 79ef391

File tree

6 files changed

+45
-10
lines changed

6 files changed

+45
-10
lines changed

unpythonic/fun.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,8 @@ def apply(f, arg0, *more, **kwargs):
304304
lst = tuple(more[-1])
305305
return lazycall(f, *(args + lst), **kwargs)
306306

307-
@mark_lazy
307+
# Not marking this as lazy-aware works better with continuations (since this
308+
# is the default cont, and return values should be values, not lazy[])
308309
def identity(*args):
309310
"""Identity function.
310311

unpythonic/lazyutil.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def mark_lazy(f):
3232

3333
def islazy(f):
3434
"""Internal. Return whether the function f is marked as lazy."""
35-
# special-case the expanded let form to support the lazify/curry combo
35+
# special-case "_let" for lazify/curry combo when let[] expressions are present
3636
return hasattr(f, "_lazy") or (hasattr(f, "__name__") and f.__name__ == "_let")
3737

3838
def lazycall(f, *thunks, **kwthunks):

unpythonic/syntax/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,8 +1669,6 @@ def g(x):
16691669
16701670
**CAUTION**: Argument passing by function call, and let-bindings are
16711671
currently the only binding constructs to which auto-lazification is applied.
1672-
1673-
**CAUTION**: This macro is experimental, not intended for production use.
16741672
"""
16751673
with dyn.let(gen_sym=gen_sym):
16761674
return (yield from _lazify(body=tree))

unpythonic/syntax/tailtools.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from ..it import uniqify
3232
from ..fun import identity
3333
from ..tco import trampolined, jump
34+
from ..lazyutil import islazy, mark_lazy
3435

3536
# -----------------------------------------------------------------------------
3637
# Implicit return. This performs a tail-position analysis of function bodies.
@@ -127,6 +128,7 @@ def chain_conts(cc1, cc2, with_star=False): # cc1=_pcc, cc2=cc
127128
"""Internal function, used in code generated by the continuations macro."""
128129
if with_star: # to be chainable from a tail call, accept a multiple-values arglist
129130
if cc1 is not None:
131+
@mark_lazy
130132
def cc(*value):
131133
return jump(cc1, cc=cc2, *value)
132134
else:
@@ -137,12 +139,14 @@ def cc(*value):
137139
cc = cc2
138140
else: # for inert data value returns (this produces the multiple-values arglist)
139141
if cc1 is not None:
142+
@mark_lazy
140143
def cc(value):
141144
if isinstance(value, tuple):
142145
return jump(cc1, cc=cc2, *value)
143146
else:
144147
return jump(cc1, value, cc=cc2)
145148
else:
149+
@mark_lazy
146150
def cc(value):
147151
if isinstance(value, tuple):
148152
return jump(cc2, *value)

unpythonic/syntax/test/test_lazify.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from ...it import flatten
66
from ...collections import frozendict
77

8-
from ...syntax import macros, lazify, lazyrec, let, letseq, letrec, curry, local
8+
from ...syntax import macros, lazify, lazyrec, let, letseq, letrec, curry, local, \
9+
continuations, call_cc
910
from ...syntax import force
1011

1112
# doesn't really override the earlier curry import, the first one went into MacroPy's macro registry
@@ -323,9 +324,8 @@ def derp(a, b, c):
323324
assert apply(derp, 1, 2, (3/0,)) == (1, 2)
324325

325326
# introducing the HasThon programming language (it has 100% more Thon than popular brands)
326-
# with curry, lazify:
327-
with lazify:
328-
with curry:
327+
# If you want a continuation-enabled HasThon, use "with continuations, curry, lazify".
328+
with curry, lazify:
329329
def add3(a, b, c):
330330
return a + b + c
331331
assert add3(1)(2)(3) == 6
@@ -342,4 +342,32 @@ def f(a, b):
342342
assert letrec[((c, 42), (d, 1/0), (e, 2*c)) in [local[x << f(e)(d)],
343343
x/2]] == 42
344344

345+
# works also with continuations
346+
# - also conts are transformed into lazy functions
347+
# - cc built by chain_conts is treated as lazy, **itself**; then it's up to
348+
# the continuations chained by it to decide whether to force their args.
349+
# - the default cont ``identity`` is strict, so it will force return values
350+
with continuations, lazify:
351+
k = None
352+
def setk(*args, cc):
353+
nonlocal k
354+
k = cc
355+
return args[0]
356+
def doit():
357+
lst = ['the call returned']
358+
*more, = call_cc[setk('A', 1/0)] # <-- this 1/0 goes into setk's args
359+
return lst + [more[0]]
360+
assert doit() == ['the call returned', 'A']
361+
# We can now send stuff into k, as long as it conforms to the
362+
# signature of the assignment targets of the "call_cc".
363+
assert k('again') == ['the call returned', 'again']
364+
# beware; if the cont tries to read the 1/0, that will lead to lots of
365+
# head-scratching, as the error will appear to come from this line
366+
# with no further debug info. (That's a limitation of the CPS conversion
367+
# technique combined with Python's insistence that there must be a line
368+
# and column in the original source file where the error occurred.)
369+
#
370+
# this 1/0 is sent directly into "more", as the call_cc returns again
371+
assert k('thrice', 1/0) == ['the call returned', 'thrice']
372+
345373
print("All tests PASSED")

unpythonic/tco.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ def baz():
131131
from sys import stderr
132132

133133
from .regutil import register_decorator
134+
from .lazyutil import islazy, mark_lazy, force, lazycall
134135

136+
@mark_lazy
135137
def jump(target, *args, **kwargs):
136138
"""A jump (noun, not verb).
137139
@@ -149,7 +151,7 @@ def jump(target, *args, **kwargs):
149151
**kwargs:
150152
Named arguments to be passed to `target`.
151153
"""
152-
return _jump(target, args, kwargs)
154+
return _jump(force(target), args, kwargs)
153155

154156
class _jump:
155157
"""The actual class representing a jump.
@@ -237,7 +239,7 @@ def trampoline(*args, **kwargs):
237239
f = function
238240
while True:
239241
if callable(f): # general case
240-
v = f(*args, **kwargs)
242+
v = lazycall(f, *args, **kwargs)
241243
else: # inert-data return value from call_ec or similar
242244
v = f
243245
if isinstance(v, _jump):
@@ -259,6 +261,8 @@ def trampoline(*args, **kwargs):
259261
if callable(function):
260262
# fortunately functions in Python are just objects; stash for jump constructor
261263
trampoline._entrypoint = function
264+
if islazy(function):
265+
trampoline = mark_lazy(trampoline)
262266
return trampoline
263267
else: # return value from call_ec or similar do-it-now decorator
264268
return trampoline()

0 commit comments

Comments
 (0)