
On Wed, Jan 20, 2016 at 01:58:46PM -0500, Terry Reedy wrote:
On 1/20/2016 11:48 AM, Guido van Rossum wrote:
But 'shared' and 'local' are both the wrong words to use here. Also probably this should syntactically be tied to the function header so the time of evaluation is clear(er).
Use ';' in the parameter list, followed by name=expr pairs. The question is whether names after are initialized local variables, subject to rebinding at runtime, or named constants, with the names replaced by the values at definition time. In the former case, a type hint could by included. In the latter case, which is much better for optimization, the fixed object would already be typed.
def f(int a, int b=1; int c=2) => int
I almost like that. The problem is that the difference between ; and , is visually indistinct and easy to mess up. I've occasionally typed ; in a parameter list and got a nice SyntaxError telling me I've messed up, but with your suggestion the function will just silently do the wrong thing. I suggest a second "parameter list": def func(a:int, b:int=1)(c:int)->int: ... is morally equivalent to: def func(a:int, b:int=1, c:int=c)->int: ... except that c is not a parameter of the function and cannot be passed as an argument: func(a=0, b=2) # okay func(a=0, b=2, c=1) # TypeError We still lack a good term for what the (c) thingy should be called. I'm not really happy with either of "static" or "capture", but for lack of anything better I'll go with capture for the moment. So a full function declaration looks like: def NAME ( PARAMETERS ) ( CAPTURES ) -> RETURN-HINT : (Bike-shedders: do you prefer () [] or {} for the list of captures?) CAPTURES is a comma-delimitered list of local variable names, with optional type hint and optional bindings. Here are some examples: # Capture the values of x and y from the enclosing scope. # Both x and y must exist at func definition time. def func(arg)(x, y): # inside the body of func, x and y are locals # Same as above, except with type-hinting. # If x or y in the enclosing scope are not floats, # a checker should report a type error. def func(arg)(x:float, y:float): # inside the body of func, x and y are locals # Capture the values of x and y from the enclosing scope, # binding to names x and z. # Both x and y must exist at func definition time. def func(arg)(x, z=y): # inside the body of func, x and z are locals # while y would follow the usual scoping rules # Capture a copy of the value of dict d from the enclosing scope. # d must exist at func definition time. def func(arg)(d:dict=d.copy()): # inside the body of func, d is a local If a capture consists of a name alone (or a name plus annotation), it declares a local variable of that name, and binds to it the captured value of the same name in the enclosing scope. E.g.: x = 999 def func()(x): # like x=x x += 1 return (x, globals()['x']) assert func() == (1000, 999) x = 0 assert func() == (1000, 0) If a capture consists of a name = expression, the expression is evaluated at function definition time, and the result captured. y = 999 def func()(x=y+1): return x assert func() == 1000 del y assert func() == 1000 Can we make this work with lambda? I think we can. The current lambda syntax is: lambda params: expression e.g. lambda x, y=y: x+y Could we keep that (for backwards compatibility) but allow parens to optionally surround the parameter list? If so, then we can allow an optional second set of parens after the first, allowing captures: lambda (x)(y): x+y The difference between lambda x,y=y: ... and lambda (x)(y): ... is that the first takes two arguments, mandatory x and optional y (which defaults to the value of y from the enclosing scope), while the second only takes one argument, x. -- Steve