Expand the discussion of nested suite variants
This commit is contained in:
parent
1d9af129be
commit
66b59db2c8
167
pep-0403.txt
167
pep-0403.txt
|
@ -216,27 +216,6 @@ reference to the associated function or class definition without creating an
|
|||
actual name binding in the current scope.
|
||||
|
||||
|
||||
Why not a nested suite?
|
||||
-----------------------
|
||||
|
||||
The problem with using a full nested suite is best described by reviewing
|
||||
PEP 3150. It's ridiculously hard to implement properly, and creates way
|
||||
too many situations where there are two ways to do it (almost any construct
|
||||
that can be expressed with ordinary imperative code could instead be
|
||||
expressed using a given statement).
|
||||
|
||||
By contrast, the decorator inspired syntax explicitly limits the new
|
||||
feature to cases where it should actually improve readability, rather than
|
||||
harming it. As in the case of the original introduction of decorators, the
|
||||
idea of this new syntax is that if it *can* be used (i.e. the local name
|
||||
binding of the function is completely unnecessary) then it *should* be used.
|
||||
|
||||
While a non-decorator based alternative could be considered (e.g. a nested
|
||||
"suite" that actually allowed only a single class or function definition),
|
||||
it seems excessive to introduce a completely new concept when it is
|
||||
possible to use a variant of the existing decorator syntax instead.
|
||||
|
||||
|
||||
Keyword Choice
|
||||
--------------
|
||||
|
||||
|
@ -300,7 +279,9 @@ operation normally implied by a function or class definition.
|
|||
The one potentially tricky part is changing the meaning of the references to
|
||||
the statement local function or namespace while within the scope of the
|
||||
``in`` statement, but that shouldn't be too hard to address by maintaining
|
||||
some additional state within the compiler.
|
||||
some additional state within the compiler (it's much easier to handle this
|
||||
for a single name than it is for an unknown number of names in a full
|
||||
nested suite).
|
||||
|
||||
|
||||
More Examples
|
||||
|
@ -383,9 +364,32 @@ rather enlightening process of investigation.
|
|||
Rejected Concepts
|
||||
=================
|
||||
|
||||
A previous incarnation of this PEP (see [1]) proposed a much uglier syntax
|
||||
that (quite rightly) was not well received. The current proposal is
|
||||
significantly easier both to read and write.
|
||||
To avoid retreading previously covered ground, some rejected alternatives
|
||||
are documented in this section.
|
||||
|
||||
|
||||
Omitting the decorator prefix character
|
||||
---------------------------------------
|
||||
|
||||
Earlier versions of this proposal omitted the ``@`` prefix. However, without
|
||||
that prefix, the bare ``in`` keyword didn't associate the clause strongly
|
||||
enough with the subsequent function or class definition. Reusing the
|
||||
decorator prefix and explicitly characterising the new construct as a kind
|
||||
of decorator clause is intended to help users link the two concepts and
|
||||
see them as two variants of the same idea.
|
||||
|
||||
|
||||
Anonymous Forward References
|
||||
----------------------------
|
||||
|
||||
A previous incarnation of this PEP (see [1]) proposed a syntax where the
|
||||
new clause was introduced with ``:`` and the forward reference was written
|
||||
using ``@``. Feedback on this variant was almost universally
|
||||
negative, as it was considered both ugly and excessively magical::
|
||||
|
||||
:x = weakref.ref(target, @)
|
||||
def report_destruction(obj):
|
||||
print("{} is being destroyed".format(obj))
|
||||
|
||||
A more recent variant always used ``...`` for forward references, along
|
||||
with genuinely anonymous function and class definitions. However, this
|
||||
|
@ -403,11 +407,114 @@ cases::
|
|||
a = calculate_a()
|
||||
b = calculate_b()
|
||||
|
||||
Another past alternative omitted the ``@`` prefix. However, without that
|
||||
prefix, the bare ``in`` keyword didn't associate the clause strongly
|
||||
enough with the subsequent function or class definition. Reusing the
|
||||
decorator prefix and explicitly characterising the new construct as a kind
|
||||
of decorator clause should address that problem.
|
||||
|
||||
Using a nested suite
|
||||
--------------------
|
||||
|
||||
The problems with using a full nested suite are best described in
|
||||
PEP 3150. It's comparatively difficult to implement properly, the scoping
|
||||
semantics are hard to explain and it creates quite a few situations where
|
||||
there are two ways to do it without clear guidelines for choosing between
|
||||
them (as almost any construct that can be expressed with ordinary imperative
|
||||
code could instead be expressed using a given statement). While the PEP did
|
||||
propose some new PEP 8 guidelines to help address that last problem, the
|
||||
difficulties in implementation and explanation are not so easily dealt with.
|
||||
|
||||
By contrast, the decorator inspired syntax in this PEP explicitly limits the
|
||||
new feature to cases where it should actually improve readability, rather
|
||||
than harming it. As in the case of the original introduction of decorators,
|
||||
the idea of this new syntax is that if it *can* be used (i.e. the local name
|
||||
binding of the function is completely unnecessary) then it *should* be used.
|
||||
|
||||
Another possible variant of this idea is to keep the decorator based
|
||||
*semantics* of this PEP, while adopting the prettier syntax from PEP 3150::
|
||||
|
||||
x = weakref.ref(target, report_destruction) given:
|
||||
def report_destruction(obj):
|
||||
print("{} is being destroyed".format(obj))
|
||||
|
||||
There are a couple of problems with this approach. The main issue is that
|
||||
this syntax variant uses something that looks like a suite, but really isn't
|
||||
one. A secondary concern is that it's not clear how the compiler will know
|
||||
which name(s) in the leading expression are forward references (although
|
||||
that could potentially be addressed through a suitable definition of the
|
||||
suite-that-is-not-a-suite in the languge grammar).
|
||||
|
||||
There's yet another possibility which would require names to be explicitly
|
||||
lifted out of the private namespace, eliminating much of the implementation
|
||||
complexity discussed in PEP 3150 (as only identifiers would be exported,
|
||||
rather than arbitrary subexpressions)::
|
||||
|
||||
x = weakref.ref(target, report_destruction) given report_destruction from:
|
||||
def report_destruction(obj):
|
||||
print("{} is being destroyed".format(obj))
|
||||
|
||||
or even::
|
||||
|
||||
x = weakref.ref(target, f) given report_destruction as f from:
|
||||
def report_destruction(obj):
|
||||
print("{} is being destroyed".format(obj))
|
||||
|
||||
This approach actually has much to recommend it. It's as powerful as
|
||||
PEP 3150 while being substantially simpler both to implement and to
|
||||
explain. The main downside is that it requires even *more* repetition
|
||||
of names than is needed with the proposed ``@in`` decorator based syntax
|
||||
or with the status quo where the function is defined and bound to a local
|
||||
name prior to its sole use.
|
||||
|
||||
Some more examples using this last variant::
|
||||
|
||||
sorted_list = sorted(original, key=f) given f from:
|
||||
def f(item):
|
||||
try:
|
||||
return item.calc_sort_order()
|
||||
except NotSortableError:
|
||||
return float('inf')
|
||||
|
||||
funcs = [adder(i) for i in range(10)] given adder from:
|
||||
def adder(i):
|
||||
return lambda x: x + i
|
||||
|
||||
environ = _createenviron() given _createenviron from:
|
||||
def _createenviron():
|
||||
... # 27 line function
|
||||
|
||||
c = math.sqrt(a*a + b*b) given a, b from:
|
||||
a = calculate_a()
|
||||
b = calculate_b()
|
||||
|
||||
dispatch[MyClass] = f given f from:
|
||||
def f():
|
||||
...
|
||||
|
||||
f() given f from:
|
||||
def f():
|
||||
...
|
||||
|
||||
One interesting outcome of such an approach is that it allows the semantics
|
||||
of function decoration to be represented almost exactly as a specific
|
||||
case of the new syntax::
|
||||
|
||||
# @deco2
|
||||
# @deco1
|
||||
# def name():
|
||||
# ...
|
||||
|
||||
name = f given f from:
|
||||
# Decorator expressions are evaluated first, in code order
|
||||
_d2 = deco2
|
||||
_d1 = deco1
|
||||
|
||||
# The base function object is defined
|
||||
def name():
|
||||
...
|
||||
|
||||
# Decorators are applied innermost first
|
||||
f = _d2(_d1(name))
|
||||
|
||||
Hmm, perhaps PEP 3150 isn't *quite* as dead as I thought, although the
|
||||
triple repetition of names is still a rather large downside.
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
|
Loading…
Reference in New Issue