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