pre-PEP: Simple Thunks
Bengt Richter
bokr at oz.net
Sun Apr 17 03:31:34 EDT 2005
On Sat, 16 Apr 2005 18:46:28 -0700, Brian Sabbey <sabbey at u.washington.edu> wrote:
[...]
>> In that case, my version would just not have a do, instead defining the do suite
>> as a temp executable suite, e.g., if instead
>>
>>
>> we make an asignment in the suite, to make it clear it's not just a calling thing, e.g.,
>>
>> do f(27, 28):
>> x = stuff()
>>
>> then my version with explict name callable suite would be
>>
>> def f(thunk, a, b):
>> # a == 27, b == 28
>> before()
>> thunk()
>> after()
>>
>> set_x():
>> x = stuff() # to make it plain it's not just a calling thing
>>
>> f(set_x, 27, 28)
>> # x is now visible here as local binding
>>
>> but a suitable decorator and an anonymous callable suite (thunk defined my way ;-) would make this
>>
>> @f(27, 28)
>> (): x = stuff()
>>
>
>Hmm, but this would require decorators to behave differently than they do
>now. Currently, decorators do not automatically insert the decorated
>function into the argument list. They require you to define 'f' as:
>
>def f(a, b):
> def inner(thunk):
> before()
> thunk()
> after()
> return inner
>
>Having to write this type of code every time I want an thunk-accepting
>function that takes other arguments would be pretty annoying to me.
>
Yes, I agree. That's the way it is with the decorator expression.
Maybe decorator syntax is not the way to pass thunks to a function ;-)
My latest thinking is in terms of suite expressions, ::<suite> being one,
and actually (<arglist>):<suite> is also a suite expression, yielding a thunk.
So the call to f could just be written explicitly with the expression in line:
f((():x=stuff()), 27, 28)
or
f(():
x = stuff()
done = True
,27, 28) # letting the dedented ',' terminate the ():<suite> rather than parenthesizing
or
safe_open((f):
for line in f:
print f[:20]
,'datafile.txt', 'rb')
That's not too bad IMO ;-)
>>>
>>> Thunks can also accept arguments:
>>>
>>> def f(thunk):
>>> thunk(6,7)
>>>
>>> do x,y in f():
>>> # x==6, y==7
>>> stuff(x,y)
>>
>> IMO
>> @f
>> (x, y): stuff(x, y) # like def foo(x, y): stuff(x, y)
>>
>> is clearer, once you get used to the missing def foo format
>>
>
>OK. I prefer a new keyword because it seems confusing to me to re-use
>decorators for this purpose. But I see your point that decorators can be
>used this way if one allows anonymous functions as you describe, and if
>one allows decorators to handle the case in which the function being
>decorated is anonymous.
I tend to agree now about using decorators for this.
With thunk calling parameter and extra f calling parameters, in line would look like
f((x,y):
# x==6, y==7
stuff(x, y)
,'other' ,'f' ,args)
I guess you could make a bound method to keep the thunk
dothunk_n_all = f.__get__((x, y):
stuff(x,y)
,type(():pass))
and then call that with whatever other parameter you specified for f
do_thunk_n_all(whatever)
if that seemed useful ;-)
>
>>>
>>> The return value can be captured
>>>
>> This is just a fallout of f's being an ordinary function right?
>> IOW, f doesn't really know thunk is not an ordinary callable too?
>> I imagine that is for the CALL_FUNCTION byte code implementation to discover?
>
>yes
>
>>
>>> def f(thunk):
>>> thunk()
>>> return 8
>>>
>>> do t=f():
>>> # t not bound yet
>>> stuff()
>>>
>>> print t
>>> ==> 8
>> That can't be done very well with a decorator, but you could pass an
>> explicit named callable suite, e.g.,
>>
>> thunk(): stuff()
>> t = f(thunk)
>
>But not having to do it that way was most of the purpose of thunks.
I forgot that ():<suite> is an expression
t = f(():stuff()) # minimal version
or
final_status = safe_open((f):
for line in f:
print f[:20]
,'datafile.txt', 'rb')
<snip>
>
>It wouldn't be a problem to use 'return' instead of 'continue' if people
>so desired, but I find 'return' more confusing because a 'return' in 'for'
>suites returns from the function, not from the suite. That is, having
>'return' mean different things in these two pieces of code would be
>confusing:
>
>for i in [1,2]:
> return 10
>
>do i in each([1,2]):
> return 10
But in my syntax,
each((i):
return 10
,[1,2])
Um, well, I guess one has to think about it ;-/
The thunk-accepter could pass the thunk a mutable arg to
put a return value in, or even a returnvalue verse thunk?
def accepter(thk, seq):
acquire()
for i in seq:
thk(i, (retval):pass)
if retval: break
release()
accepter((i, rvt):
print i
rvt(i==7) # is this legal?
, xrange(10))
Hm, one thing my syntax does, I just noticed, is allow you
to pass several thunks to a thunk-accepter, if desired, e.g.,
(parenthesizing this time, rather than ending ():<suite> with
dedented comma)
each(
((i): # normal thunk
print i),
((j): # alternative thunk
rejectlist.append(j)),
[1,2])
<snip>
>I see what you're getting at with decorators and anonymous functions, but
>there are a couple of things that, to me, make it worth coming up with a
>whole new syntax. First, I don't like how one does not get the thunk
>inserted automatically into the argument list of the decorator, as I
>described above. Also, I don't like how the return value of the decorator
>cannot be captured (and is already used for another purpose). The fact
>that decorators require one more line of code is also a little bothersome.
>Decorators weren't intended to be used this way, so it seems somewhat
>hacky to do so. Why not a new syntax?
>
Ok, I agree this does not fit decorators well. And I do agree that a nice
syntax that sugars over the creation and passing of thunks makes for clean
simple cases, but I would like access to the non-sugar primitives too, which for
do <opt assignment> <thunk arg list> in <callable>(<arglist>):
<thunk-defining suite>
IIUC translates to my
< opt assignment> <callable>(((<thunk arg list>):
<thunk-defining-suite>),<arglist>)
With possible paren dropping and white space insertion if you want to arrange things.
With the primitives, I can pass multiple thunks to an accepter, and put them in
specific arg positions. I also can also bind it and use it to get a side effect
out of a generator expression that now has its own scope, e.g.,
count = 0
counter(count): count +=1
list(i for i in xrange(20) if counter() or True)
Interestingly, this ought to work, right?
stopper(i): if i>5: raise StopIteration
list(stopper(i) or i for i in xrange(20))
All untested hadwaving, of course ;-)
Regards,
Bengt Richter
More information about the Python-list
mailing list