1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/docs/closures.txt Wed Dec 04 16:17:20 2013 +0100
1.3 @@ -0,0 +1,100 @@
1.4 +Micropython does not support closures, but the way they might be supported is
1.5 +to provide outer namespaces as implicit parameters, somewhat like default
1.6 +parameters, to functions in a way that is more or less supported by "dynamic"
1.7 +functions (which provide attributes to record additional state).
1.8 +
1.9 +Consider the following example:
1.10 +
1.11 +def outer(x):
1.12 + def inner(y):
1.13 + return x + y
1.14 + return inner
1.15 +
1.16 +f = outer(10) # f -> inner
1.17 +x = f(5) # x -> 15
1.18 +
1.19 +This could be represented as follows:
1.20 +
1.21 +def outer(x):
1.22 + # <self> refers to the local namespace
1.23 + def inner(y, <outer>=<self>):
1.24 + return <outer>.x + y
1.25 + return inner
1.26 +
1.27 +In other words, where a function uses names from another namespace, usage of
1.28 +such names must be qualified. Note that outer must be "dynamic" itself to
1.29 +support external access to its contents, and all accessed names must be
1.30 +exported by the dynamic portion of the function namespace.
1.31 +
1.32 +What this means is that outer must be dynamically allocated, at least as far
1.33 +as its exported portion is concerned, and inner must also be dynamically
1.34 +allocated as far as the reference to outer is concerned.
1.35 +
1.36 +Defaults and Allocation
1.37 +-----------------------
1.38 +
1.39 +The above is somewhat similar to how defaults are supported, and this is
1.40 +illustrated as follows:
1.41 +
1.42 +def outer(x):
1.43 + def inner(y, x=x):
1.44 + return x + y
1.45 + return inner
1.46 +
1.47 +This needs to be represented as follows:
1.48 +
1.49 +def outer(x):
1.50 + def inner(y, <self>.x=x):
1.51 + return <self>.x + y
1.52 + return inner
1.53 +
1.54 +Here, any invocation of the returned function can omit the x argument, and
1.55 +this argument is actually acquired from the instance of the inner function
1.56 +returned by outer. However, this freezes the value of x during the invocation
1.57 +of outer, whereas a closure has the "default" as the outer namespace and
1.58 +accesses x through that namespace, picking up any modifications that may have
1.59 +occurred subsequently.
1.60 +
1.61 +Here is an example of differing behaviour between closures and "static"
1.62 +defaults:
1.63 +
1.64 +def outer(x): def outer(x):
1.65 + def inner(y): def inner(y, x=x): # stores x
1.66 + return x + y return x + y # gets the stored value
1.67 + x += 1 x += 1 # changes x only in outer
1.68 + return inner return inner
1.69 +
1.70 +Closures and Allocation
1.71 +-----------------------
1.72 +
1.73 +In principle, the support for closures would follow from the support for
1.74 +defaults, but instead of specific names being populated in dynamic functions,
1.75 +entire namespaces would be referenced.
1.76 +
1.77 +In addition, the mechanics of accessing referenced namespaces would involve a
1.78 +special parameter for accessing the dynamic state, just as is done with
1.79 +defaults.
1.80 +
1.81 +def outer(x):
1.82 + <self> = namespace(x=x) # allocate the locals dynamically
1.83 +
1.84 + def inner(y, <self>): # the special parameter exposes state
1.85 + return <self>.<outer>.x + y # and thus the outer namespace
1.86 +
1.87 + <self>.x += 1
1.88 + return makedynamic(inner, <outer>=<self>)
1.89 +
1.90 +Note that the outer function, unlike a function that has externally set
1.91 +defaults, must have its local namespace dynamically allocated upon every
1.92 +invocation. In comparison, a dynamic function providing defaults needs only to
1.93 +be allocated when its definition is evaluated.
1.94 +
1.95 +When the dynamic inner function is called, its state is provided through the
1.96 +special parameter:
1.97 +
1.98 +f = outer(10) # returns inner with extra state referencing <outer with x=11>
1.99 +f(5) # invokes inner with y=5, <self>=state
1.100 + # evaluates <self>.<outer>.x
1.101 + # which is <outer with x=11>.x
1.102 + # which is x=11
1.103 + # returning 16