On Fri, May 29, 2020 at 12:36:20PM +1200, Greg Ewing wrote:
> On 29/05/20 12:17 am, Richard Damon wrote:
> >But default values for arguments are really part of the responsibility
> >for the caller, not the called function. The classic implementation
> >would be that the caller passes all of the explicitly,
> 
> I would say that's really a less-than-ideal implementation,
> that has the consequence of requiring information to be put
> in the header that doesn't strictly belong there.

"Strictly" according to whom?

Well, you obviously :-)

For 30 years, Python's early bound default arguments have been part of 
the function signature. It's only late bound default arguments which are 
hidden inside the body. So you are arguing that for 30 years Python has 
put information into the function header that doesn't belong there.

Where else would you put the parameter defaults, if not in the parameter 
list?


> To my mind, the signature consists of information that a
> static type checker would need to verify that a call is valid.

A static type checker would have to know whether a parameter has a 
default value, even if it doesn't know what that default value is.

So you are arguing that the ideal (using your word from above) function 
signature for, let's say, the `open` builtin should look something like 
this:

    open(file, 
         mode= <REDACTED>, 
         buffering= <REDACTED>, 
         encoding= <REDACTED>, 
         errors= <REDACTED>,
         newline= <REDACTED>, 
         closefd= <REDACTED>, 
         opener= <REDACTED>)

plus type information, which I cannot be bothered showing. This ideal 
arrangement tells the static type checker everything it needs to know to 
determine whether a call is valid or not:

- the name and order of the parameters;
- their types (pretend I showed them);
- whether they can be omitted or not;
- whether or not the function takes positional only parameters
  (in this case, it doesn't);
- whether or not the function takes vararg and keyword varargs 
  as well (in this case, it doesn't);

but none of the things that it doesn't need to know, such as the actual 
default value.

[Aside: well, *almost* everything: it doesn't tell the static checker 
whether the function is in scope or not.]

As a user of that function, I need to know the same things the static 
checker needs to know, *plus* the actual default values. I'm not sure 
why you care more about the type checker than the users of the function. 
Even if you couldn't care less about my needs, presumably you are a user 
of the function too.


> That does not include default values of arguments. The fact
> that a particular value is substituted if an argument is
> omitted is part of the behaviour of the function, not part
> of its signature.

That depends on what you define as the *function* signature and whether 
you equate it with the function's *type* signature.

(Or dare I say it, whether you conflate it with the type signature.)

Consider two almost identical functions:

    def add(a:int, b:int=0)->str:
        return a+b

    def add(a:int, b:int=1)->str:
        return a+b

These two functions have:

- identical names;
- identical parameter lists;
- identical type signatures (int, int -> int);
- identical function bodies;

but they are different functions, with similar but different 
semantics (the first defaults to a no-op, the second doesn't). Where 
does the difference lie?

It's not specifically in the implementation (the body), that's 
identical. It's not in the *type* signature, we agree on that.

I want to say that it is a difference in the *function* signature, which 
consists of not just the types, but also the function name and 
parameters including defaults.

Not only is this a useful, practical way of discussing the difference, 
it matches 30 years of habit in the Python community. Reading your 
post is literally the first time it has dawned on me that anyone would 
want to exclude default values from the notion of function signature.

(By the way, in Forth, function signatures are comments showing stack 
effects, and they're only for the human reader.)


> By putting default values in the header, we're including a
> tiny bit of behavioural information.

Behavioural information is already present as soon as the header 
includes whether or not parameters can be, or must be, given by name or 
position, and whether or not it accepts varargs.

In languages with procedures (or void functions) as well as functions, 
that behavioural information is also present in the signature.

In languages with checked exceptions, that behavioural information is 
likewise present in the signature.

What's your problem with these?


> By distinguishing between
> one-time and per-call evaluation of defaults, we're adding a
> bit more behavioural information.

Yes.


> Where do we draw the line? How much of the behaviour of the
> function do we want to move into the header?

Well obviously we don't draw the line until we've fallen down the 
slippery slope and the entire body of the function is part of the 
header!

*wink*

I think there are serious practical difficulties in moving much more 
behavioural information into the header. Not that there's much more we 
might want in the function header. Checked exceptions perhaps? Not me 
personally. What else *could* we move into the header?


-- 
Steven
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/JODBVL4L5K7DA3WTB4QSV4JVHSSJRJVT/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to