pre-PEP: Suite-Based Keywords - syntax proposal

Bengt Richter bokr at oz.net
Mon Apr 18 23:31:17 EDT 2005


On 17 Apr 2005 21:48:47 -0700, "Kay Schluehr" <kay.schluehr at gmx.net> wrote:
<snip>
>> >
>> I like this. But how would you put "where args:" and "where kw:" if
>you needed both?
>> also, is it looking back to see the '*' or '**' to do (::x=1).values
>vs. (::x=1)
>> and how about (::x=1).keys() or (::x=1).items() ? And what if you
>wanted to pass
>> (::x=1) as a dict object without ** expansion into a keyword dict?
>>
>> Maybe we need asterisks on both ends. e.g.,
>>
>>     foo(dct, values, *args, **kw):
>>        where **dct:
>>           x=1
>>        where *values:
>>           x=2
>>        where *args:
>>           x=3
>>        where **kw:
>>           x=4
>
>Yes... Why not?

On second thought, "where **x:" might as well be "where x:" since ** is a good default,
but I think "where x:" looks too much like and ordinary suite will follow, rather than
capturing the bindings created by the suite and binding that dict to x temporarily.
I would rather see "where x::" to indicate that, which also ties in consistently with
my syntax for for thunks as suite expressions, in that that now gives named def-like
statement versions to all the anonymous versions, when you include this <name>::<suite>
along with <name>(<thunkarglist>):<thunksuite> and <name> def(<defarglist>):<anonymous_def_suite>

Of course that last is equivalent to an ordinary def, and I can't see using it, since a def will
do everywhere except immediately following where, and the assignment form is clearer there, e.g.:

    foo(123) where foo=def(x):
        print x

as a way to define a transient function and call it


so with that in mind, and I just realized we can do away with * specifiers
if we move that job to where the dict of bindings is used:

       foo(dct, values.values(), *args.values(), **kw):  #XXX ':' used as function call trailer not good
          where dct::     # get suite as dict in all cases
             x=1
          where values::
             x=2
          where args::
             x=3
          where kw::
             x=4

Now using the same syntax to define temporarily named thunks for the arg list:

       thunk_accepter(tk1, tk2, [1,2], **kw) where: # now where: can introduce an ordinary where suite
           tk1(i):
               print i
           tk2(j):
               rejectlist.append(j)
           kw::
               x = 1
               y = 2
 
This would be equivalent to     

       thunk_accepter(tk1, tk2, [1,2], **kw) where:
           tk1 = (i):
               print i
           tk2 = (j):
               rejectlist.append(j)
           kw = ::
               x = 1
               y = 2
      
Which might look more "mainstream", and be ok for multiple values, but for a single thunk,
which is probably the most frequent use case, a statement should be able to
end in  "where <single assignment or name binder>:" e.g.,

       foo(x) where x=1 # minimal silly

or named thunk definition syntax, which binds the name and uses a suite, e.g.,

       safe_open(tk, filepath, filemode) where tk(f):
           for line in f:
               print line[:40]

would be nice and concise. And it looks pretty mainstream, in that
tk(f) looks just like the thunk call that safe_open will do to run the suite
in the local namespace. Might even look good to Brian? ;-)

But note that "where tk(f):<suite>"  is really equivalent to the assignment form
"where tk = (f):<suite>" much like def foo(): return 'hi' is equivalent to foo = lambda: 'hi'
(and my thunk definition syntax derives from def foo(<arglist>):<suite> minus "def foo" ;-)

>
>>
>> But that still doesn't give you, e.g.,
>>
>>        foo(keys) where:
>>            keys=sorted((::
>>                from interesting.module import *
>>            ).keys())
>
>This particular statement won't work anyway inside a where-clause
>because
>"from *" must be called from module level. You would have to import
>interesting.module before:
>
>        import interesting.module
>        foo(keys) where:
>            keys = sorted(interesting.module.__dict__).keys()
>
>But it wasn't ever intended to put arbitrary statements in a kw-suite,
>right?
Sure, as arbitrary as inside a function. Import * was a bad example. I never use that,
so I shouldn't have used it in an example, even though on my system it does it, grudgingly:

 >>> def foo():
 ...     from math import *
 ...     return vars().keys()
 ...
 <stdin>:1: SyntaxWarning: import * only allowed at module level
 >>> foo()
 ['pow', 'cosh', 'ldexp', 'hypot', 'tan', 'asin', 'log', 'fabs', 'floor', 'sqrt', 'frexp', 'degre
 es', 'pi', 'log10', 'sin', 'modf', 'atan', 'ceil', 'sinh', 'cos', 'e', 'tanh', 'radians', 'atan2
 ', 'fmod', 'exp', 'acos']
 >>>

I should have put a safe import inside, using your example
         foo(keys) where:
             import interesting.module
             keys = sorted(interesting.module.__dict__).keys()

If you import it outside of that scope, you will have 'interesting' bound outside.

I'm not saying go wild and do weird things, I'm just saying don't impose arbitrary
restrictions on adults. These explorations are just to illustrate possibilities,
not to advocate them all as good conventional use.

>
>> I like clean sugar, but I still want to be able to
>> get at the general primitives to compose them in ways
>> that can't be anticipated until a use case comes up.
>> And then if the primitives are inaccessible, one is
>> out of luck or doomed to workaround hacking ;-)
>
>You can always consider "where" as function of a statement. The only
>restriction You have to make is to bind "where" to a function-call i.e.
>regard each function as a function object with a where() method.

Um, I think that's too narrow for where. Consider

    foo = f1; bar=f2; x=k1; y=k2
    foo(x)*bar(y)[3].attr

now should

    foo(x)*bar(y)[3].attr where:
        foo = f1; bar=f2; x=k1; y=k2

act any different? How do you spell this as a where bound to a function call?

>
>
>f(*args,**kw ).where( <specifier>,
>                        (<assignment-statement>|
>                         <function-definition>|
>                         <class-definition>)*
>                    )
>
>But that is not a loss of generality because a free (:: x=1 ) can be
>mapped onto
>
>  dict(**kw).where(**kw, x=1)
>
>and that is
>
>  dict(**kw) where **kw:
>       x=1


so for
    foo(*args) where:
        args = dict(**kw).keys() where **kw:
                                      x=1

but it's much easier (now with explorations and ideas above ;-) to write

    foo(*args.values()) where args::
        x=1

>
>I don't see any case where this translation fails. Only if it comes to
>functional composition like
>
>  f(g(...(h(:: x=1)...))
>
>it may be awkward to expand this into a nested where clauses. You might
>probably define the argument not in a suite ;)

There are probably some things that won't look reasonable no matter what ;-)

But to summarize, I think we should try to write a real grammar for all this,
and show how it would fit in with the real python grammar.

I think I should make a separate post for this ;-)
see
   grammar for where/letting/with and suite expressions (thunks etc)

   
Regards,
Bengt Richter



More information about the Python-list mailing list