fix line breaks in recursion.txt
Haldean Brown
3 years ago

47 | 47 | ! t 1 |

48 | 48 | } |

49 | 49 | |

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

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

51 | definition. However, since t is a local binding, it's never actually | |

52 | assigned to a name that persists past its own scope. To handle this, we | |

53 | transform t to take itself as a parameter; callers then call t by | |

54 | passing it itself. The resulting function then looks like: | |

55 | 55 | |

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

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

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

59 | 59 | } |

60 | 60 | |

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

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

62 | callers have to know that t needs to be passed itself as its first | |

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

64 | 64 | |

65 | 65 | ! t t 1 |

66 | 66 | |

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

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

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

69 | function, with itself already applied (for brevity, B is used to | |

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

71 | 71 | |

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

73 | 73 | |

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

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

75 | without requiring a double-definition of the function, as the function | |

76 | must be its own value: | |

77 | 77 | |

78 | 78 | 000 VALUE &B |

79 | 79 | 001 APPLY 000 000 |

80 | 80 | |

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

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

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

83 | example: | |

83 | 84 | |

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

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

86 | 87 | ! inner |

87 | 88 | } |

88 | 89 | |

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

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

91 | for our syntactic transformations. The closure transformation takes | |

92 | inner from that given in the previous listing to this: | |

92 | 93 | |

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

94 | 95 | |

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

100 | 101 | x |

101 | 102 | |

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 is something more like: | |

103 | This looks okay, but doesn't properly work, because the reference to | |

104 | inner in the body of the function doesn't pass any of the closed-over | |

105 | parameters. What we want is something more like: | |

105 | 106 | |

106 | 107 | : top = \x -> { |

107 | 108 | : inner_ = \x inner y -> inner (+ x y) |

109 | 110 | : inner_with_inner_ = inner_with_x_ inner_with_x_ |

110 | 111 | } |

111 | 112 | |

112 | But that doesn't work either. Hm. Instead, we introduce a secret atom type, | |

113 | SELFREF, which is a reference to the innermost enclosing function. So we end up | |

114 | rewriting inner to be: | |

113 | But that doesn't work either. Instead, we introduce a secret atom type, | |

114 | SELFREF, which is a reference to the innermost enclosing function. So | |

115 | we end up rewriting inner to be: | |

115 | 116 | |

116 | 117 | : inner = (\x y -> { |

117 | 118 | : inner = SELFREF x |

118 | 119 | ! inner (+ x y) |

119 | 120 | }) x |

120 | 121 | |

121 | Now the "inner" on the inside doesn't actually reference a named binding on the | |

122 | outside. When this is compiled down to a graph, SELFREF will be transformed | |

123 | into a VALUE node that points at the value currently being compiled. | |

122 | Now the "inner" on the inside doesn't actually reference a named binding | |

123 | on the outside. When this is compiled down to a graph, SELFREF will be | |

124 | transformed into a VALUE node that points at the value currently being | |

125 | compiled. | |

124 | 126 | |

125 | 127 | With more-heavily-nested recursion, this ends up transforming: |

126 | 128 |