more fully-thought-out design for inner recursion
Haldean Brown
5 years ago

11 | 11 | - more tests for rational functions |

12 | 12 | - short-aliases for arithmetic |

13 | 13 | - matches against nonatomic expressions (? f x { . A => ...) |

14 | - fix all applications in codegen to set recursive_app = false |

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

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

77 | 77 | value. |

78 | ||

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: | |

83 | ||

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

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

86 | . => t 0 | |

87 | }) *t | |

88 | ||

89 | With the asterisk here being shorthand for is-recursive-parameter. Then, | |

90 | in the code-generation stage, when the code generation visitor sees an | |

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

92 | ||

93 | (f *f) | |

94 | ||

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. | |

98 | ||

99 | &f = f | |

100 | ((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 | |

111 | } | |

112 | ||

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

114 | ||

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

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

117 | ||

118 | And their associated recusion transformations are: | |

119 | ||

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

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

122 | ||

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. To get around this, the | |

133 | code generation stage needs to examine the full apply tree to check if | |

134 | an apply chain contains a recursion parameter, and if it does, it needs | |

135 | to extract the leftmost function value into its own top-level value. | |

136 | ||

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

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

139 | level: | |

140 | ||

141 | ~ p | |

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

143 | ||

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

145 | ||

146 | 000 INPUT 0 | |

147 | 001 LOAD "p:f" | |

148 | 002 CONST 0 | |

149 | 003 APPLY 001 002 | |

150 | ||

151 | and the LOAD breaks the cycle. |

113 | 113 | { |

114 | 114 | struct ubik_ast_expr *head; |

115 | 115 | struct ubik_ast_expr *tail; |

116 | /* Marks whether this represents an application | |

117 | * of a function to itself. For more | |

118 | * information, see docs/recursion.txt. */ | |

119 | bool recursive_app; | |

116 | 120 | } apply; |

117 | 121 | struct |

118 | 122 | { |

78 | 78 | apply1->scope = to_match->scope; |

79 | 79 | apply1->apply.head = native_func_atom; |

80 | 80 | apply1->apply.tail = ctor_name_atom; |

81 | apply1->apply.recursive_app = false; | |

81 | 82 | apply1->loc = res->loc; |

82 | 83 | |

83 | 84 | res->expr_type = EXPR_APPLY; |

84 | 85 | res->scope = to_match->scope; |

85 | 86 | res->apply.head = apply1; |

86 | 87 | res->apply.tail = to_match_copy; |

88 | res->apply.recursive_app = false; | |

87 | 89 | |

88 | 90 | return OK; |

89 | 91 | } |