On 29/05/20 11:49 pm, Steven D'Aprano wrote:
Where else would you put the parameter defaults, if not in the parameter list?
In some part of the function that's not the parameter list. :-) There are many ways it could be done. I've suggested one already (a special assignment statement). Another would be to have a special "defaults" section in the body.
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.
It has to know that the parameter is *optional*, yes. It doesn't need to know what the default value is, or even whether it has a default value at all. Even if the default value is available to the type checker, it's just going to ignore it. The only thing that *might* make use of it is the code generator.
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.
I need to know a whole lot of *other* things about the function too, but we don't expect to find them all in the header. We have docstrings for that. I'm not saying that default values shouldn't be in the header. I'm saying you can't argue that because *some* information about default values currently happens to be in the header, then *all* of it should be. That just doesn't follow.
That depends on what you define as the *function* signature and whether you equate it with the function's *type* signature.
It seems to me that the whole concept of a signature only makes sense in relation to static analysis. If you're not doing static analysis of some kind, then *everything* about a function is behaviour. In Python, you can attempt to call any function with any parameters, and something will happen, even if it's just raising an exception. The only reason for singling out certain characteristics of a function and calling them its "signature" is if you want to do some sort of static analysis about whether a call is valid. So, I think that "signature" always implies "type signature", for some notion of "type".
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
Certainly. But that doesn't prove that the default values have to be in the header! In an alternative universe, they could be written def add(a:int, [b:int])->str: b ?= 0 return a+b def add(a:int, [b:int])->str: b ?= 1 return a+b The default value of b would then have to be conveyed by the documentation -- just like everything else about what the function does.
I want to say that it is a difference in the *function* signature,
Then you would have to come up with some principle for deciding what is part of this "function signature", other than "whatever happens to be in the header".
Not only is this a useful, practical way of discussing the difference, it matches 30 years of habit in the Python community.
It's certainly a habit, and one which spans more than one language. But I think we need to recognise it as just that -- a habit, not a law.
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.
I can understand that. I even surprised myself when I came to that conclusion, because I hadn't really thought about it before. I think we're so used to seeing default values in the header in other languages, such as C++, that we've come to regard it as part of the natural order. But I think C++ is a bad example to follow, because there the default values are in the header for *implementation* reasons. There are lots of other places in C++ as well where implementation issues leak into the language design (e.g. the fact that all member functions of a class need to be declared in its header file, including private ones -- I don't think anyone regards *that* as a desirable feature!) It's also worth remembering that in early versions of C, not even the *types* of the parameters were written in the header -- e.g. you wrote functions like int foo(a, b) float a; char *b; { .... } So none of this stuff is universal across languages.
What's your problem with [including behavioural information in the header]?
Only that the more stuff you put in the header, the more cluttered it becomes, so we need a good reason for every item we include. The only objective rule I can think of for deciding whether to put something in the header is whether it's needed for static analysis. (And even that depends on exactly what static analysis you want to perform.) If you have a better one, I'd be interested to hear it.
Not that there's much more we might want in the function header. Checked exceptions perhaps?
No! Not checked exceptions! Stay away! [Holds up crucifix, garlic and holy water.] -- Greg