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