micropython

Annotated docs/closures.txt

780:287e94386042
2014-02-23 Paul Boddie Added closure PEP references. syspython-as-target
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/