thunks (for all the fish)

On Wed, 29 Jan 2003 23:49:05 -0500, guido@python.org (Guido van Rossum) wrote:
Wow. I started thinking about this last night, and already there have been many good suggestions. I make no claims of what follows to be Pythonic or even implementable. Assume a tiny man lives in your computer, he answers to the name "Python Thunk", and he always does the right thing, very quickly. OK, a 'thunk' is a generalized generator created from an indented suite. A 'thunk' lets us react to control statements inside the thunk (or the lack of control statements). iter0 = thunk.make_g_iter( [global] [, local] ) makes this generalized iterator, and iter0.next() can return one of these tuples: ('continue', None, None), ('break', None, None), ('nested_continue', None, None), ('nested_break', None, None), ('yield', value, None), ('return', value, dict), ('dead', None, None) Everything except 'yield' implies that 'dead' will be returned next. Nothing is done to prevent these from being mixed and matched in ways that would never otherwise be allowed. In a looping situation, it would be typical to call thunk.make_g_iter() for each step of the loop. These: thunk.dict( [global] [, local] ) thunk.call( [global] [, local] ) # execute as function always do the right thing by examining the output of .next(). If they smell a 'generator' ('dead' not returned at the second .next()), they raise an Exception. There are some subtleties of global and local I am sure I am missing. However, I don't see a problem with 'global' statements in thunks. This is the cast of characters: 1 function: anon_thunk() x = f(anon_thunk()) + g(anon_thunk()): ...thunk body... the less said about this the better 1 line noise operation: magical colon x = expr: ...thunk body... which is the same as: x = expr(anon_thunk()): ...thunk body... This seems to be popular. Magical line noise ( :*.""",)r" ) is hard for new users to look up the documentation. 2 keywords: thunk_d, thunk_c thunk_d for "function-like" things thunk_c for "class-like" (???!!!) things thunk_d: takes "function arguments" in the parenthesis after the name and turns them into a 'arg_helper' function that turns (*args, **kw_args) into a dict suitable for use as a local namespace thunk_d name(func_a, func_kw_a): expr: ...thunk body... thunk_c: takes "base classes or whatever" in the parenthesis after the name and turns them into args and kw_args thunk_c name(a, kw_a): expr: ...thunk body... 2 control keywords: nested_continue, nested_break these work their way up the frame stack, looking for loops to harass (even if they might be loops in other thunks). Would a thunk always get the chance to handle 'nested_continue/nested_break' before moving up the frame stack? I can't get my head around how "try: except:" might be used to handle 'nested_continue/nested_break'. Happily, we assumed an infinitely wise, infinitely fast creature controlling all this. *** synchronized example (much hand waving): for i in sequence: syncronized(mylock): if ...condition...: continue ...more code... if ...condition...: break ...yet more code... if ...condition...: return 42 'synchronized(mylock)' gets the thunk, then does thunk.execute(), and sees if it gets back a 'continue', 'break', or 'return'. Upon seeing a 'continue' or 'break' it issues a 'nested_continue' or 'nested_break' command, which magically works its way up the frame stack to the loop, always being careful to do the right thing. *** more hand waving key, record = recordset_loop('rs1'): if no_more_keys: return None, None if key in skip_keys: continue print key print record if key == good_key: return key, record thunk.next() doesn't care if it is a function, loop, generator, whatever, so why should we? There is the equivalent of a finally: inside recordset_loop, so our recordset gets closed upon Exception. There is great flexibility in programmatically handling control statements with Python code. I can see how if you use the thunk as a body of a loop, almost any control statement could have a contextual meaning, making it valid to mix them in otherwise unsupported ways. Should this be allowed? If somebody is willing to write the code to handle all these cases, why shouldn't it be allowed? *** anon_thunk syntax: x = expr3(expr2(expr1(anon_thunk()))): ...thunk body... x = by_hook(anon_thunk()) or by_crook(anon_thunk()): ...thunk body... a,b = anon_thunk().call(): c = long_expression1(x,y) d = long_expression2(x,y) return min(c,d), max(c,d) print anon_thunk().dict()['c']: a = 'hello' b = 12 c = '%s%i' % (a,b) This isn't a syntax so much as it is a lack of syntax. Here is a shambling horror: the super-lambda. *** property example: p = property: """thunk.__doc__ docstring""" def get(self): ...body... def set(self, value): ...body... Whenever I have an object whose name is a key in a dict, I always make the name an attribute in the object too. Usually it is redundant, but when the object is considered outside of the context of the dict, where else would you get its name from? This is why I prefer the uglier "thunk_d myprop: property:"; property gets a chance to the name. thunk_d myprop: property: """thunk.__doc__ docstring""" def get(self): ...body... def set(self, value): ...body... 'property' is passed tuple ('myprop', None, thunk), and the property is made from thunk.__doc__ and thunk.dict(). *** staticmethod example: thunk_d mymethod(a1, a2, kw_a1=11, kw_a2=13): staticmethod: """thunk.__doc__ docstring""" ...function body... 'staticmethod' is passed tuple ('mymethod', arg_helper, thunk), where arg_helper(1,2,3) == { 'a1':1, 'a2':2, 'kw_a1':3, 'kw_a2':13 } 'staticmethod' makes the method from thunk.__doc__, thunk.call(), arg_helper; the method gets assigned to 'mymethod'. *** class "interface" example: thunk_c klass1(base1, base2): interface1 + interface2: """thunk.__doc__ docstring""" ...class-like body... interface3 = interface1 + interface2, and interface3 gets passed (klass1, (base1, base2), kw_args=(), thunk), and makes a class from thunk.dict() *** other stuff if anon_thunk(): # not allowed ...thunk body... # not allowed for i in expr1: # not allowed ...thunk body... # not allowed x = [: # not allowed ...thunk body... # not allowed ] # not allowed x = [anon_thunk()]: # sure, why not? ...thunk body... # sure, why not?

On Thu, 2003-01-30 at 19:28, Manuel Garcia wrote the subject line above: +0 on the pun, BTW. :)

On Thu, 30 Jan 2003 19:28:33 -0800, news@manuelmgarcia.com (Manuel Garcia) wrote:
Oh yeah, global and local will probably come from the thunk itself. When the thunk is created, a suitable global and local is attached. Usually the global would be used as is, perhaps the local would be modified with some extra pairs before passing it to thunk.make_g_iter().
Would something like this be helpful?: max_length = 25 skip_keys = ['1234', '2345'] double_keys = ['9876', '8765'] g = rs1.recordset_to_generator(BOF=1, EOF=1): if list_length >= max_length: break if BOF: return 'AAA', None if EOF: return 'ZZZ', None if key in skip_keys: continue if key in double_keys: yield '%s-00' % (key,), record return '%s-01' % (key,), record else: return key, record You would make a generator in this strange way, with a combination of returns and yields. You could use break and continue for the loop, and booleans could be put in the local to help sneak in extra values. Again, any 'try: finally:' logic could be hidden inside. One problem is that all these 'extra' behaviors would be hard to document, unless you gave a long list of examples for people who might use a 'recordset_to_generator()' you created.

On Thu, 2003-01-30 at 19:28, Manuel Garcia wrote the subject line above: +0 on the pun, BTW. :)

On Thu, 30 Jan 2003 19:28:33 -0800, news@manuelmgarcia.com (Manuel Garcia) wrote:
Oh yeah, global and local will probably come from the thunk itself. When the thunk is created, a suitable global and local is attached. Usually the global would be used as is, perhaps the local would be modified with some extra pairs before passing it to thunk.make_g_iter().
Would something like this be helpful?: max_length = 25 skip_keys = ['1234', '2345'] double_keys = ['9876', '8765'] g = rs1.recordset_to_generator(BOF=1, EOF=1): if list_length >= max_length: break if BOF: return 'AAA', None if EOF: return 'ZZZ', None if key in skip_keys: continue if key in double_keys: yield '%s-00' % (key,), record return '%s-01' % (key,), record else: return key, record You would make a generator in this strange way, with a combination of returns and yields. You could use break and continue for the loop, and booleans could be put in the local to help sneak in extra values. Again, any 'try: finally:' logic could be hidden inside. One problem is that all these 'extra' behaviors would be hard to document, unless you gave a long list of examples for people who might use a 'recordset_to_generator()' you created.
participants (3)
-
Chad Netzer
-
Manuel Garcia
-
Manuel M. Garcia