Returning none

Tim Peters tim_one at email.msn.com
Fri Sep 3 20:09:40 EDT 1999


[C.Laurence Gonsalves, continues with his drive to make a distinction
 between functions and procedures]

Skipping most of this, since it's gotten repetitive.

> ...
> Maybe I got a bit over-emotional at your response. I really think
> it would have been better if you stated why you think it is the way
> it is,and what's wrong with my ideas, rather than just flatly saying
> that "if that idea were any good it would already be implemented".

I asked whether you imagined Guido had not considered it when he designed the
language.  Your paraphrases of that are getting increasingly unrecognizable
<wink/frown>.

>> You should study eval_code2 (in Python/ceval.c) if you want to
>> suggest implementations.

> ...
> What happens when exceptions are raised?  What value is returned
> then? And is "NULL" used for anything?

See above.

> ...
> Why would you make a tuple containing the result of a function that
> doesn't have a result?  These should be three separate statements.
> Semicolons are just as easy to type as commas on most keyboards. :-)

Part of the cost in making a change is judging how much pain it will cause to
existing code.  Since the "f(), g(), h()" style works fine today, you can't
dismiss it out of hand on the grounds that anyone doing that "should" have done
it some other way.

I've certainly seen code like that.  Perhaps it was a typo and they meant to
write ";".  Perhaps they're unreasonably fond of C's comma operator.  Maybe
they're just insane.  But for purposes of guessing total pain, it doesn't
really matter what their motivations or shortcomings are -- it's legal Python
today.

Whether or not it's good style to write that kind of thing explicitly, it
happens implicitly regardless.  For example, the builtin "map" applies a
function across a sequence, and builds a list of the values returned by each
function application.  Sometimes map is used as a shorthand for a for-loop,
purely for side-effect (e.g., map(operator.setitem, ...)), and the result
sequence (typically [None]*len(input_sequence)) is simply discarded.

So now what?  Toss map and introduce (say) funcmap and procmap?  Keep map and
say it's an exception to the rules?  Twist the rules into a subtle enough state
that an excruciatingly careful reading implicitly exempts map and map-like
functions (or are they map-like procedures <wink>)?

Features generally don't exist in isolation, and you have to look at all the
consequences, not just the one that attracts you at first sight.

> ...
> <grumble> That sounds like a good reason to remove array bounds
> checking as well... someone's going to notice when the program
> starts acting weird, but it might be "later than you might hope for".

I agreed before that the situations are analogous -- except that there was no
additional cost to raise an error in the bounds-checking case (less cost).

> Seriously, the current behaviour doesn't do any error checking at all.
> It returns a goofy value with the hopes that someone will spot the
> problem.

Agreed before also that it's an insecurity.  It is, though, in my view, a tiny
insecurity compared to letting an out-of-bounds array reference occur silently
(more benefit).  And it's trivial compared to the insecurities of runtime
AttributeErrors and NameErrors.

BTW, there was a bizarre debate in Python's first public year, with one side
arguing that None should (like a signaling NaN in 754 arithmetic) always raise
an error whenever it's touched, and the other side arguing that (like a quiet
NaN) None should never raise an error but instead silently spread to every
expression it's a part of.  Both sides lost <wink>.

> ...
> I really don't see why raising an exception when appropriate would
> be so hard.

Given that you're willing to break existing code, and introduce new keywords,
and change the grammar, and add new opcodes, and fiddle the eval loop, and sign
up for a currently-unknown number of currently-unknown unexpected interactions
with other features, it's *not* that hard.  It's getting to those givens that's
hard.  The only post-1.0 language change even close to being that sweeping was
the introduction of lambda expressions -- but they were brand new and broke
only code that already used the name "lambda", didn't require a new opcode, and
required no changes to the eval loop.  It was one of three keywords ever added
(one was later removed again, and "exec" was a builtin function before
elevation to statementhood).

IOW, Python's evolution has been very conservative.  That's a fact.  All
proposals have to contend with that, and a new keyword alone is an
extraordinary event in Pythonland.

> ...
> So how about this:
>
> - functions return "nothing" (not None) if you fall off the end or say
>   "return" without an argument.
> - expressions are no longer statements (grammar change)
> - there is a new type of "procedure call" statement (grammar change)
> - function calls that are in an expression cause an exception to be
>   raised if they don't actually return anything (eg: NoResultError)
> - the root* function call of a procedure call statement is permitted
>   to return nothing at all
>
> * by "root" function call I'm referring to the function call that
>   would be the root of the expression tree, if the procedure call
>   statement was treated as an expression. This is easy to determine
>   syntactically.

After making them precise, I expect this kind of scheme would work.

> ...
> I haven't yet checked to see how a return of "nothing" could be
> indicated internally, but I'll do that soon.

NULL is already used for error, a PyObject* must be returned, and for
portability Python never plays casting tricks with pointers.  So you'll need to
invent a new internal object type, or turn the solution on its head and make
the callee responsible for griping if it's called from the wrong kind of
context (which you could do easily enough by passing around a new "result
required", "result forbidden", "do as thou wilt" enum).

> ...
> If you think there is a problem with these ideas, please let me know
> why. I'm not trying to start a fight here. I like Python a lot, and
> would like to see it improved. If you think it's better the way it
> already is, convince me.

I'm never going to convince you that you don't have a problem with returning
None by mistake, and you're never going to convince me that I do (if I did, I
expect I would have noticed it at least once after this many years <wink>).

You judge the benefits to be higher than I do, and the costs less.  "Better"
depends on their ratio.  I'm happy enough with how it is today; although I
certainly wouldn't object if the hole could be plugged in a compatible way that
didn't create new problems.

and-if-it-could-have-been-it-already-would<wink>-ly y'rs  - tim






More information about the Python-list mailing list