start writing about recursive closures
Haldean Brown
5 years ago

1 | 1 | Haldean Brown First draft: Nov 2016 |

2 | 2 | Last updated: Nov 2016 |

3 | 3 | |

4 | Status: Complete | |

4 | Status: In progress | |

5 | 5 | |

6 | 6 | ------------------------------------------------------------------------ |

7 | 7 | |

47 | 47 | ! t 1 |

48 | 48 | } |

49 | 49 | |

50 | In this example, the closure transformation should take t and turn it | |

51 | into this: | |

50 | In this example, t needs access to its own value from within its definition. | |

51 | However, since t is a local binding, it's never actually assigned to a name | |

52 | that persists past its own scope. To handle this, we transform t to take itself | |

53 | as a parameter; callers then call t by passing it itself. The resulting | |

54 | function then looks like: | |

52 | 55 | |

53 | : t = (\t x -> ? { | |

56 | : t = \$t x -> ? { | |

54 | 57 | . eq x 0 => "ok\n" |

55 | . => t 0 | |

56 | }) t | |

57 | ||

58 | But now we find ourselves referencing t before we've defined it, and we | |

59 | can quickly get into a circular reference. Here, we transform t into a | |

60 | top-level anonymous function with its own value: | |

61 | ||

62 | : anonymous-t = | |

63 | \t x -> ? { | |

64 | . eq x 0 => "ok\n" | |

65 | . => t 0 | |

58 | . => $t $t 0 | |

66 | 59 | } |

67 | 60 | |

68 | ! { | |

69 | : t = anonymous-t (anonymous-t anonymous-t) | |

70 | ! t 1 | |

71 | } | |

61 | This poses a problem for the callers of this function, though; now all callers | |

62 | have to know that t needs to be passed itself as its first argument, so the | |

63 | immediate value of the block would be transformed to: | |

72 | 64 | |

73 | In so doing, we can fully define anonymous-t without any | |

74 | self-references, and ditto with t. Note that in the actual | |

75 | implementation of this feature, anonymous-t does not have a name in the | |

76 | global namespace; it is fully anonymous and is only defined by its | |

77 | value. | |

65 | ! t t 1 | |

78 | 66 | |

79 | In order to accomplish this, the closure transformation code marks the | |

80 | application of the function to itself as the application of a recursion | |

81 | parameter, but otherwise performs the closure transformation normally. | |

82 | In the above example, this then becomes: | |

67 | But this is clunky; we don't want to have to transform every call site that | |

68 | references t. Instead, we transform t to a partially-applied function, with | |

69 | itself already applied (for brevity, B is used to represent the body of the | |

70 | function t as given in the previous listing): | |

83 | 71 | |

84 | : t = (\t x -> ? { | |

85 | . eq x 0 => "ok\n" | |

86 | . => t 0 | |

87 | }) $t | |

72 | : t = (\$t x -> B) (\$t x -> B) | |

88 | 73 | |

89 | With the dollar sign here being shorthand for is-recursive-parameter. | |

90 | Then, in the code-generation stage, when the code generation visitor | |

91 | sees an apply statement with a recursion parameter, it transforms: | |

74 | Note that this can be very succinctly represented in Ubik bytecode, without | |

75 | requiring a double-definition of the function, as the function must be its own | |

76 | value: | |

92 | 77 | |

93 | (f $f) | |

78 | 000 VALUE &B | |

79 | 001 APPLY 000 000 | |

94 | 80 | |

95 | Into a new top-level value inserted into the workspace and a reference | |

96 | to that value, replacing the recursion parameter with a reference to the | |

97 | same value. | |

81 | This seems nice in practice, but there's one more sticky bit: the original | |

82 | closure transformation. Let's take a slightly more complicated example: | |

98 | 83 | |

99 | &f = f | |

100 | ((const &f) ((const &f) (const &f))) | |

101 | ||

102 | The closure transformation is maintained on f, allowing it to | |

103 | self-recurse without any self-references. | |

104 | ||

105 | This gets a little more complicated when there are multiple closed | |

106 | parameters being applied. Take this useless function as an example: | |

107 | ||

108 | { | |

109 | : x = 8 | |

110 | : f = \y -> f x | |

84 | : top = \x -> { | |

85 | : inner = \y -> inner (+ x y) | |

86 | ! inner | |

111 | 87 | } |

112 | 88 | |

113 | Both of these would be valid closure transformations for f: | |

89 | Never mind that this is infinitely recursive: it's a nice simple example for | |

90 | our syntactic transformations. The closure transformation takes inner from that | |

91 | given in the previous listing to this: | |

114 | 92 | |

115 | : f = (\f x y -> f x) *f x | |

116 | : f = (\x f y -> f x) x *f | |

93 | : inner = (\x y -> inner (+ x y)) x | |

117 | 94 | |

118 | And their associated recusion transformations are: | |

95 | Which works fine. The recursion transformation would then take it to: | |

119 | 96 | |

120 | : anonymous-f = \f x y -> f x | |

121 | : f = anonymous-f anonymous-f x | |

97 | : inner = | |

98 | (\inner x y -> inner inner (+ x y)) | |

99 | (\inner x y -> inner inner (+ x y)) | |

100 | x | |

122 | 101 | |

123 | : anonymous-f = \x f y -> f x | |

124 | : f = anonymous-f x anonymous-f | |

125 | ||

126 | There are no statements here that look exactly like our (f *f) pattern | |

127 | above, though, in the example where f is not the first closure | |

128 | parameter. We end up with: | |

129 | ||

130 | ((f x) *f) | |

131 | ||

132 | And (f x) isn't extractable, because x is local. We also can't | |

133 | successfully partially-apply this function to itself, because we would | |

134 | have to apply the arguments ahead of the recursive application. We get | |

135 | around this by forcing the recursive application to happen before any | |

136 | other closure application. | |

137 | ||

138 | Note that top level bindings do not need this transformation, because | |

139 | they can reference themselves by name. So this function at the top | |

140 | level: | |

141 | ||

142 | ~ p | |

143 | : f = \t -> f 0 | |

144 | ||

145 | Gets turned into the following pseudo-bytecode: | |

146 | ||

147 | 000 INPUT 0 | |

148 | 001 LOAD "p:f" | |

149 | 002 CONST 0 | |

150 | 003 APPLY 001 002 | |

151 | ||

152 | and the LOAD breaks the cycle. | |

102 | This looks okay, but doesn't properly work, because the reference to inner in | |

103 | the body of the function doesn't pass any of the closed-over parameters. What | |

104 | we want |