[Python-ideas] Delay evaluation of annotations

Stephen J. Turnbull turnbull.stephen.fw at u.tsukuba.ac.jp
Sat Sep 24 15:07:05 EDT 2016


אלעזר writes:

 > But _some_ people (medium-level, Steven, whose main language is
 > probably not Python) will not even know [function names are looked
 > up at runtime, and so the called function may not be why you think
 > it is] is the case.

And the dinosaurs will have returned by independent evolution by the
time it matters to them (unless it's a deliberate attack, in which
case people at that level would be toast anyway).

But I think you're completely missing what people are trying to tell
you.  You shouldn't be so concerned with refuting their arguments
because it doesn't matter.  No matter how many points you amass for
technique, you're going to get creamed on style points anyway.  It's
like this:

(1) Python is a "consenting adults" language, and that is presumed by
    its development culture.  The goal is not to stop people from
    creating "functions that look like recursions but aren't" on
    purpose; it's to make it easy for them to write recursive
    functions if they want to.  From your example, that goal is
    obviously satisfied.  Nobody who matters wants to go farther than
    that in Python.

    The reason one can create "functions that look like recursions but
    aren't" is because another of Python's goals is to ensure that all
    things -- specifically including functions -- are objects that can
    be manipulated "the same way" where appropriate -- in this case,
    saving off the original function object somewhere then rebinding
    the original name to something else.[1]  Granted, we don't go so
    far as Lisp where expressions are lists that you can manipulate
    like any other list, but aside from the fact that the code itself
    is an opaque object, functions are no different from other
    objects.  Even builtins:

    Python 3.6.0a4 (default, Sep  3 2016, 19:21:32) 
    >>> def help(*ignored, **paid_no_attention):
    ...  print("Ouch, you just shot off your foot!")
    ... 
    >>> help(help)
    Ouch, you just shot off your foot!
    >>> 

    Shooting off your opposite extremity by redefining builtin classes
    is left as an exercise for the reader.

    All of this is a matter of the general attitude of pragmatism and
    bias toward simplicity of implementation (both enshrined in the
    Zen of Python).

(2) You keep talking about others being lost in terminology, but in
    the context of Python discussions, you have a really big problem
    yourself.  You use the phrase "just an annotation" as though that
    means something, but there is nothing like a "just an <anything>"
    in Python discourse, not in the sense that "once we introduce
    <anythings>s, they can be anything we want".  The Language
    Reference defines what things are possible, and truly new ones are
    rarely added.

    This is deliberate.  Another design principle is Occam's Razor,
    here applied as "new kinds of thing shall not spring up like
    whiskers on Barry's chin."  Yes, function annotations need new
    syntax and so are a new kind of thing to that extent.  *Their
    values don't need to be,* and even the annotations themselves are
    implemented in the preferred way for "new things" (a dunder on an
    existing type).  Since it's new syntax, it's language-level, and
    so the values are going to be something already defined in the
    language reference.  "Expression resolving to object to be saved
    in an attribute on the function" seems to be as close to "anything
    you want" as you're gonna get without a new kind of thing.

(3) Python has a very simple model of expressions.  The compiler turns
    them into code.  The interpreter executes that code, except in the
    case where it is "quoted" by the "def" or "lambda" keywords, in
    which case it's stored in an object (and in the case of "def",
    registered in a namespace).

    As Nick admits, you could indeed argue that initializations and
    annotation values *could* consistently be turned into "thunks"
    (stored code objects, we already have those) in attributes on the
    function object.  But

    (1) that's an extension of the model (admittedly slight since
        functions, which already do that for their bodies, are
        involved -- but see Nick's reply for the hidden difficulties
        due to normal handling of namespaces in Python), and

    (2) it's a clear pessimization in the many cases where those
        values are immutable or very rarely mutated, and the use case
        (occasional) of keeping state in mutable values.  The thunk
        approach is more complex, for rather small benefit.  Re "small
        benefit", IMHO YMMV, but at least with initialization Guido is
        on record saying it's the RightThang[tm] (despite a propensity
        of new users to write buggy initializations).

(4) Chris argues that "compile to thunk" is incoherent, that
    expressions in function bodies are no different than anywhere else
    -- they're evaluated when flow of control reaches them.  AFAICS
    that *still* doesn't rule out having the compiler recognize the
    syntax and produce code that returns thunks instead of ordinary
    values, but Chris's point makes that seem way too magical to me.

(5) This points up the fact that Python is thoroughly dynamic.  It's
    not just that types adhere to objects rather than variables, but
    the whole attitude toward language design and implementation is.
    A variable not defined because it's on the path not taken, or even
    a function: they just don't exist as far as the interpreter is
    concerned -- there's no way to find them from Python.  That's not
    true in say C: if you have a powerful enough debugger, you can
    even call a function defined, but never referenced, in the code.

    So while we'd be happy for people familiar with "statically-typed
    languages" to enjoy the benefits of using Python for some of their
    work, we can't help them if they can't shake off that attitude
    when using Python.  Making things seem intuitive (which here
    translates to "familiar", as usual) to them is just misleading.
    Python doesn't work that way, and often enough, that matters.

(6) As you point out: of course, thunks are more general than values
    (in fact, without instructions that move them around, in computers
    a value is just a tree falling in a forest with noone to hear).
    But maximum generality is not necessarily an important goal, even
    if it makes some things prettier.  Allow me to quote the late
    Saunders Mac Lane: "[G]ood general theory does not search for the
    maximum generality, but for the right generality."

(7) Re Nick's comment about backward compatibility on the high bar
    having a G degree of difficulty, I'm sure you don't disagree with
    the principle of avoiding compatibility breaks.

    But while that's probably the argument that defeats this proposal
    here, I think that even looking forward from the time before
    releasing Python 3.0, the decision would be the same.  That is, I
    think the decision to go with the simpler "evaluate to object"
    model was one of the right decisions at that time, for the reasons
    above.  Your proposal of "evaluate to thunk" (possibly
    incorporating the property-based magic Alexander proposed) might
    be right *too*, but it's far from obviously better to me.  I see
    nothing there that would be likely to have dissuaded the authors
    of PEP 3107, or Guido when he designed default initialization,
    from "evaluate to object".

If you don't like that philosophy, or somehow don't think it applies
here, keep trying, you may have a point.  But in this thread, IMO
you're trying to ski up a slope that's barely able to hold snow, and
wasting everybody's time.  And even if I'm wrong about wasting time
with the feature, you'll be way more persuasive if you argue in terms
of Python as it is designed (mostly deliberately so), which is the way
most of us mostly like it.  Although we do change our mind every 18
months. :-)


Footnotes: 
[1]  For maximum humor, rebind it to a different recursive function!



More information about the Python-ideas mailing list