paul@762 | 1 | Micropython does not support closures, but the way they might be supported is
|
paul@762 | 2 | to provide outer namespaces as implicit parameters, somewhat like default
|
paul@762 | 3 | parameters, to functions in a way that is more or less supported by "dynamic"
|
paul@762 | 4 | functions (which provide attributes to record additional state).
|
paul@762 | 5 |
|
paul@762 | 6 | Consider the following example:
|
paul@762 | 7 |
|
paul@762 | 8 | def outer(x):
|
paul@762 | 9 | def inner(y):
|
paul@762 | 10 | return x + y
|
paul@762 | 11 | return inner
|
paul@762 | 12 |
|
paul@762 | 13 | f = outer(10) # f -> inner
|
paul@762 | 14 | x = f(5) # x -> 15
|
paul@762 | 15 |
|
paul@762 | 16 | This could be represented as follows:
|
paul@762 | 17 |
|
paul@762 | 18 | def outer(x):
|
paul@762 | 19 | # <self> refers to the local namespace
|
paul@762 | 20 | def inner(y, <outer>=<self>):
|
paul@762 | 21 | return <outer>.x + y
|
paul@762 | 22 | return inner
|
paul@762 | 23 |
|
paul@762 | 24 | In other words, where a function uses names from another namespace, usage of
|
paul@762 | 25 | such names must be qualified. Note that outer must be "dynamic" itself to
|
paul@762 | 26 | support external access to its contents, and all accessed names must be
|
paul@762 | 27 | exported by the dynamic portion of the function namespace.
|
paul@762 | 28 |
|
paul@762 | 29 | What this means is that outer must be dynamically allocated, at least as far
|
paul@762 | 30 | as its exported portion is concerned, and inner must also be dynamically
|
paul@762 | 31 | allocated as far as the reference to outer is concerned.
|
paul@762 | 32 |
|
paul@762 | 33 | Defaults and Allocation
|
paul@762 | 34 | -----------------------
|
paul@762 | 35 |
|
paul@762 | 36 | The above is somewhat similar to how defaults are supported, and this is
|
paul@762 | 37 | illustrated as follows:
|
paul@762 | 38 |
|
paul@762 | 39 | def outer(x):
|
paul@762 | 40 | def inner(y, x=x):
|
paul@762 | 41 | return x + y
|
paul@762 | 42 | return inner
|
paul@762 | 43 |
|
paul@762 | 44 | This needs to be represented as follows:
|
paul@762 | 45 |
|
paul@762 | 46 | def outer(x):
|
paul@762 | 47 | def inner(y, <self>.x=x):
|
paul@762 | 48 | return <self>.x + y
|
paul@762 | 49 | return inner
|
paul@762 | 50 |
|
paul@762 | 51 | Here, any invocation of the returned function can omit the x argument, and
|
paul@762 | 52 | this argument is actually acquired from the instance of the inner function
|
paul@762 | 53 | returned by outer. However, this freezes the value of x during the invocation
|
paul@762 | 54 | of outer, whereas a closure has the "default" as the outer namespace and
|
paul@762 | 55 | accesses x through that namespace, picking up any modifications that may have
|
paul@762 | 56 | occurred subsequently.
|
paul@762 | 57 |
|
paul@762 | 58 | Here is an example of differing behaviour between closures and "static"
|
paul@762 | 59 | defaults:
|
paul@762 | 60 |
|
paul@762 | 61 | def outer(x): def outer(x):
|
paul@762 | 62 | def inner(y): def inner(y, x=x): # stores x
|
paul@762 | 63 | return x + y return x + y # gets the stored value
|
paul@762 | 64 | x += 1 x += 1 # changes x only in outer
|
paul@762 | 65 | return inner return inner
|
paul@762 | 66 |
|
paul@762 | 67 | Closures and Allocation
|
paul@762 | 68 | -----------------------
|
paul@762 | 69 |
|
paul@762 | 70 | In principle, the support for closures would follow from the support for
|
paul@762 | 71 | defaults, but instead of specific names being populated in dynamic functions,
|
paul@762 | 72 | entire namespaces would be referenced.
|
paul@762 | 73 |
|
paul@762 | 74 | In addition, the mechanics of accessing referenced namespaces would involve a
|
paul@762 | 75 | special parameter for accessing the dynamic state, just as is done with
|
paul@762 | 76 | defaults.
|
paul@762 | 77 |
|
paul@762 | 78 | def outer(x):
|
paul@762 | 79 | <self> = namespace(x=x) # allocate the locals dynamically
|
paul@762 | 80 |
|
paul@762 | 81 | def inner(y, <self>): # the special parameter exposes state
|
paul@762 | 82 | return <self>.<outer>.x + y # and thus the outer namespace
|
paul@762 | 83 |
|
paul@762 | 84 | <self>.x += 1
|
paul@762 | 85 | return makedynamic(inner, <outer>=<self>)
|
paul@762 | 86 |
|
paul@762 | 87 | Note that the outer function, unlike a function that has externally set
|
paul@762 | 88 | defaults, must have its local namespace dynamically allocated upon every
|
paul@762 | 89 | invocation. In comparison, a dynamic function providing defaults needs only to
|
paul@762 | 90 | be allocated when its definition is evaluated.
|
paul@762 | 91 |
|
paul@762 | 92 | When the dynamic inner function is called, its state is provided through the
|
paul@762 | 93 | special parameter:
|
paul@762 | 94 |
|
paul@762 | 95 | f = outer(10) # returns inner with extra state referencing <outer with x=11>
|
paul@762 | 96 | f(5) # invokes inner with y=5, <self>=state
|
paul@762 | 97 | # evaluates <self>.<outer>.x
|
paul@762 | 98 | # which is <outer with x=11>.x
|
paul@762 | 99 | # which is x=11
|
paul@762 | 100 | # returning 16
|
paul@780 | 101 |
|
paul@780 | 102 | References
|
paul@780 | 103 | ----------
|
paul@780 | 104 |
|
paul@780 | 105 | "PEP 227 -- Statically Nested Scopes"
|
paul@780 | 106 | http://www.python.org/dev/peps/pep-0227/
|
paul@780 | 107 |
|
paul@780 | 108 | "PEP 3104 -- Access to Names in Outer Scopes"
|
paul@780 | 109 | http://www.python.org/dev/peps/pep-3104/
|