Skip to content

Commit bf98b44

Browse files
committed
name change to make it easier to follow
1 parent 0fd3c7e commit bf98b44

File tree

3 files changed

+238
-111
lines changed

3 files changed

+238
-111
lines changed

examples/openrecursion/openrec.flx

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,76 +4,76 @@ typedef o_adde[T] = (
44
| `Add of T * T
55
);
66
#line 79 "openrecursion.fdoc"
7-
fun o_eval[T] (eval: T -> int) (term: o_adde[T]) =>
7+
fun o_evala[T] (eval: T -> int) (term: o_adde[T]) =>
88
match term with
99
| `Int j => j
1010
| `Add (a,b) => eval a + eval b
1111
endmatch
1212
;
1313
#line 91 "openrecursion.fdoc"
1414
typedef adde = o_adde[adde];
15-
fun eval (term:adde):int => o_eval[adde] eval term;
15+
fun evala (term:adde):int => o_evala[adde] evala term;
1616
#line 103 "openrecursion.fdoc"
17-
var x : adde = `Add (`Add (`Int 39, `Int 2), `Int 1);
18-
var y = eval x;
19-
println$ y;
17+
var xa : adde = `Add (`Add (`Int 39, `Int 2), `Int 1);
18+
var yaa = evala xa;
19+
println$ "ya="+yaa.str;
2020
#line 113 "openrecursion.fdoc"
2121
typedef o_sube[T] = (
2222
| o_adde[T]
2323
| `Sub of T * T
2424
);
25-
fun o_eval2[T] (eval: T -> int) (term: o_sube[T]):int =>
25+
fun o_evals[T] (eval: T -> int) (term: o_sube[T]):int =>
2626
match term with
2727
| `Sub (a,b) => eval a - eval b
28-
| o_adde[T] :>> k => o_eval eval k
28+
| o_adde[T] :>> k => o_evala eval k
2929
endmatch
3030
;
3131
#line 139 "openrecursion.fdoc"
3232
typefun o_sube_f (T:TYPE) : TYPE => o_sube[T];
3333
typedef sube = tfix<TYPE> o_sube_f;
34-
fun eval2 (term:sube):int => (fix[sube,int] o_eval2[sube]) term;
34+
fun evals (term:sube):int => (fix[sube,int] o_evals[sube]) term;
3535
#line 160 "openrecursion.fdoc"
36-
var x2 : sube = `Add (`Sub (`Int 39, `Int 2), `Int 1);
37-
var y2 = eval2 x2;
38-
var y3 = eval2 x; // ORGINAL DATA
39-
println$ y2;
40-
println$ y3;
36+
var xs : sube = `Add (`Sub (`Int 39, `Int 2), `Int 1);
37+
var yss = evals xs;
38+
var ysa = evals xa; // ORGINAL DATA
39+
println$ "ys="+yss.str;
40+
println$ "ya="+ysa.str;
4141
#line 187 "openrecursion.fdoc"
4242
typedef o_mule[T] = (
4343
| o_adde[T]
4444
| `Mul of T * T
4545
);
46-
fun o_eval3[T] (eval: T -> int) (term: o_mule[T]):int =>
46+
fun o_evalm[T] (eval: T -> int) (term: o_mule[T]):int =>
4747
match term with
4848
| `Mul(a,b) => eval a * eval b
49-
| o_adde[T] :>> k => o_eval eval k
49+
| o_adde[T] :>> k => o_evala eval k
5050
endmatch
5151
;
5252
typefun o_mule_f (T:TYPE) : TYPE => o_mule[T];
5353
typedef mule = tfix<TYPE> o_mule_f;
54-
fun eval3 (term:mule):int => (fix[mule,int] o_eval3[mule]) term;
55-
var x3 : mule = `Add (`Mul(`Int 39, `Int 2), `Int 1);
56-
var y4 = eval3 x3;
57-
var y5 = eval3 x; // ORGINAL DATA
58-
println$ y4;
59-
println$ y5;
54+
fun evalm (term:mule):int => (fix[mule,int] o_evalm[mule]) term;
55+
var xm : mule = `Add (`Mul(`Int 39, `Int 2), `Int 1);
56+
var ymm = evalm xm;
57+
var yma = evalm xa; // ORGINAL DATA
58+
println$ "ym="+ymm.str;
59+
println$ "ya="+yma.str;
6060
#line 211 "openrecursion.fdoc"
6161
typedef o_dive[T] = (
6262
| o_sube[T]
6363
| o_mule[T]
6464
| `Div of T * T
6565
);
66-
fun o_eval4[T] (eval: T -> int) (term: o_dive[T]):int =>
66+
fun o_evald[T] (eval: T -> int) (term: o_dive[T]):int =>
6767
match term with
6868
| `Div(a,b) => eval a / eval b
69-
| o_sube[T] :>> k => o_eval2 eval k
70-
| o_mule[T] :>> k => o_eval3 eval k
69+
| o_sube[T] :>> k => o_evals eval k
70+
| o_mule[T] :>> k => o_evalm eval k
7171
endmatch
7272
;
7373
typefun o_dive_f (T:TYPE) : TYPE => o_dive[T];
7474
typedef dive = tfix<TYPE> o_dive_f;
75-
fun eval4 (term:dive):int => (fix[dive,int] o_eval4[dive]) term;
76-
var x4 : dive =
75+
fun evald (term:dive):int => (fix[dive,int] o_evald[dive]) term;
76+
var xd : dive =
7777
`Add (
7878
`Mul (
7979
`Int 39,
@@ -86,11 +86,11 @@ var x4 : dive =
8686
`Int 66
8787
)
8888
;
89-
var y6 = eval4 x4;
90-
var y7 = eval4 x; // ORGINAL DATA
91-
var y8 = eval4 x2; // ORGINAL DATA
92-
var y9 = eval4 x3; // ORGINAL DATA
93-
println$ y6;
94-
println$ y7;
95-
println$ y8;
96-
println$ y9;
89+
var ydd = evald xd;
90+
var yda = evald xa; // ORGINAL DATA
91+
var yds = evald xs; // ORGINAL DATA
92+
var ydm = evald xm; // ORGINAL DATA
93+
println$ "yd="+ydd.str;
94+
println$ "ya="+yda.str;
95+
println$ "ys="+yds.str;
96+
println$ "yn="+ydm.str;

openrecursion.fdoc

Lines changed: 102 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ typedef adde = (
5555
@
5656
Notice, it is a recursive data type. The function we want is
5757
@felix
58-
fun eval (term: adde) =>
58+
fun evala (term: adde) =>
5959
match term with
6060
| `Int j => j
61-
| `Add (a,b) => eval a + eval b
61+
| `Add (a,b) => evala a + evala b
6262
endmatch
6363
;
6464
@
@@ -76,7 +76,7 @@ The type variable has eliminated the recursion.
7676

7777
And here's the open version of the function:
7878
@tangle openrec.flx
79-
fun o_eval[T] (eval: T -> int) (term: o_adde[T]) =>
79+
fun o_evala[T] (eval: T -> int) (term: o_adde[T]) =>
8080
match term with
8181
| `Int j => j
8282
| `Add (a,b) => eval a + eval b
@@ -89,7 +89,7 @@ an extra parameter which is used to eliminate the recursive calls.
8989
Now we are going to recover the original data type and function:
9090
@tangle openrec.flx
9191
typedef adde = o_adde[adde];
92-
fun eval (term:adde):int => o_eval[adde] eval term;
92+
fun evala (term:adde):int => o_evala[adde] evala term;
9393
@
9494
What we have done is close the open data type by
9595
replacing the type variable with a self-reference.
@@ -100,9 +100,9 @@ of the recursion must be specified.
100100

101101
Here is a test case:
102102
@tangle openrec.flx
103-
var x : adde = `Add (`Add (`Int 39, `Int 2), `Int 1);
104-
var y = eval x;
105-
println$ y;
103+
var xa : adde = `Add (`Add (`Int 39, `Int 2), `Int 1);
104+
var yaa = evala xa;
105+
println$ "ya="+yaa.str;
106106
@
107107

108108
@h2 Extension to subtraction
@@ -114,18 +114,18 @@ typedef o_sube[T] = (
114114
| o_adde[T]
115115
| `Sub of T * T
116116
);
117-
fun o_eval2[T] (eval: T -> int) (term: o_sube[T]):int =>
117+
fun o_evals[T] (eval: T -> int) (term: o_sube[T]):int =>
118118
match term with
119119
| `Sub (a,b) => eval a - eval b
120-
| o_adde[T] :>> k => o_eval eval k
120+
| o_adde[T] :>> k => o_evala eval k
121121
endmatch
122122
;
123123
@
124124
Notice both the type and function delegate to the existing code for addition
125125
and only add support for our new operation, subtraction.
126126

127127
@h2 Fixpoint operator.
128-
The closure of the the open type and function ic called <em>fixating</em> them.
128+
The closure of the the open type and function is called <em>fixating</em> them.
129129
What we have done is to manually introduce the self-reference.
130130
For the data type, we used a self-refering type alias; for the function
131131
we used eta-expansion.
@@ -138,7 +138,7 @@ Here's how we do it:
138138
@tangle openrec.flx
139139
typefun o_sube_f (T:TYPE) : TYPE => o_sube[T];
140140
typedef sube = tfix<TYPE> o_sube_f;
141-
fun eval2 (term:sube):int => (fix[sube,int] o_eval2[sube]) term;
141+
fun evals (term:sube):int => (fix[sube,int] o_evals[sube]) term;
142142
@
143143

144144
Here are the definitions from the standard library:
@@ -157,11 +157,11 @@ function fixpoint operator.
157157
Finally a test case:
158158

159159
@tangle openrec.flx
160-
var x2 : sube = `Add (`Sub (`Int 39, `Int 2), `Int 1);
161-
var y2 = eval2 x2;
162-
var y3 = eval2 x; // ORGINAL DATA
163-
println$ y2;
164-
println$ y3;
160+
var xs : sube = `Add (`Sub (`Int 39, `Int 2), `Int 1);
161+
var yss = evals xs;
162+
var ysa = evals xa; // ORGINAL DATA
163+
println$ "ys="+yss.str;
164+
println$ "ya="+ysa.str;
165165
@
166166

167167
It's very important to notice that the original data also works with
@@ -188,20 +188,20 @@ typedef o_mule[T] = (
188188
| o_adde[T]
189189
| `Mul of T * T
190190
);
191-
fun o_eval3[T] (eval: T -> int) (term: o_mule[T]):int =>
191+
fun o_evalm[T] (eval: T -> int) (term: o_mule[T]):int =>
192192
match term with
193193
| `Mul(a,b) => eval a * eval b
194-
| o_adde[T] :>> k => o_eval eval k
194+
| o_adde[T] :>> k => o_evala eval k
195195
endmatch
196196
;
197197
typefun o_mule_f (T:TYPE) : TYPE => o_mule[T];
198198
typedef mule = tfix<TYPE> o_mule_f;
199-
fun eval3 (term:mule):int => (fix[mule,int] o_eval3[mule]) term;
200-
var x3 : mule = `Add (`Mul(`Int 39, `Int 2), `Int 1);
201-
var y4 = eval3 x3;
202-
var y5 = eval3 x; // ORGINAL DATA
203-
println$ y4;
204-
println$ y5;
199+
fun evalm (term:mule):int => (fix[mule,int] o_evalm[mule]) term;
200+
var xm : mule = `Add (`Mul(`Int 39, `Int 2), `Int 1);
201+
var ymm = evalm xm;
202+
var yma = evalm xa; // ORGINAL DATA
203+
println$ "ym="+ymm.str;
204+
println$ "ya="+yma.str;
205205
@
206206

207207
@h2 Mixing it all together
@@ -213,17 +213,17 @@ typedef o_dive[T] = (
213213
| o_mule[T]
214214
| `Div of T * T
215215
);
216-
fun o_eval4[T] (eval: T -> int) (term: o_dive[T]):int =>
216+
fun o_evald[T] (eval: T -> int) (term: o_dive[T]):int =>
217217
match term with
218218
| `Div(a,b) => eval a / eval b
219-
| o_sube[T] :>> k => o_eval2 eval k
220-
| o_mule[T] :>> k => o_eval3 eval k
219+
| o_sube[T] :>> k => o_evals eval k
220+
| o_mule[T] :>> k => o_evalm eval k
221221
endmatch
222222
;
223223
typefun o_dive_f (T:TYPE) : TYPE => o_dive[T];
224224
typedef dive = tfix<TYPE> o_dive_f;
225-
fun eval4 (term:dive):int => (fix[dive,int] o_eval4[dive]) term;
226-
var x4 : dive =
225+
fun evald (term:dive):int => (fix[dive,int] o_evald[dive]) term;
226+
var xd : dive =
227227
`Add (
228228
`Mul (
229229
`Int 39,
@@ -236,14 +236,79 @@ var x4 : dive =
236236
`Int 66
237237
)
238238
;
239-
var y6 = eval4 x4;
240-
var y7 = eval4 x; // ORGINAL DATA
241-
var y8 = eval4 x2; // ORGINAL DATA
242-
var y9 = eval4 x3; // ORGINAL DATA
243-
println$ y6;
244-
println$ y7;
245-
println$ y8;
246-
println$ y9;
239+
var ydd = evald xd;
240+
var yda = evald xa; // ORGINAL DATA
241+
var yds = evald xs; // ORGINAL DATA
242+
var ydm = evald xm; // ORGINAL DATA
243+
println$ "yd="+ydd.str;
244+
println$ "ya="+yda.str;
245+
println$ "ys="+yds.str;
246+
println$ "ym="+ydm.str;
247247
@
248248

249+
@h1 Open/Closed Principle
250+
In "Object Oriented Software Construction" Bertran Meyer named a crucial
251+
system design principe the "Open/Closed Principle". It states that a system
252+
should have a unit of modularity which is simultaneously closed so it can
253+
be used and also open so it can be extended. This gave rise to the idea
254+
of classes which are closed object factories, but can be extended by
255+
inheritance.
256+
257+
The object oriented version of classes is a catastrophic failure bccause
258+
of severe restrictions imposed by type systems on the type of arguments of methods,
259+
which must be contravariant (or invariant), whereas usually we require covariance.
260+
261+
Open recursion solves this problem by providing types and function which are
262+
open to extension, and do not suffer fron the covariance problem because
263+
type variables are invariant.
264+
265+
The system then provides a closure operator, namely the fixpoint operator,
266+
which locks in covariant behaviour of a particular extension of the base type.
267+
The types as coproducts, which are covariant, and so base types are subtypes
268+
of extensions. This impplies the methods of an extension will continue to work
269+
with data of the base type.
270+
271+
@h2 Issues with polymorphic variants
272+
Polymorphic variants (as in Felix or Ocaml) are essential for open recursion
273+
to work. However as is the case with all structural types with user supplied
274+
componenmts, including records, compiler error diagnostics are very hard to
275+
understand due at least in part the fact that all the components must be listed
276+
to identify the type. By contrast, nominal types are easy to refer to, since
277+
they have a single unique and simple name.
278+
279+
Ocaml has made significant progress in this area by the diagnostics ability
280+
to search the symbol to table to see if a compnent set happens to have a named
281+
alias.
282+
283+
@h2 Multivariant Open Recursion
284+
In our simple example, the open version of a type has a single type variable,
285+
and the open version of a function has a functional parameter: the type variable
286+
and the paramater are eliminated by self-reference by using the fixpoint operators.
287+
288+
However in complex systems such as a compiler, we usually have main types,
289+
and auxilliay types they depend on; sometimes, we have two of more major
290+
types which are convoluted and inseparable: in these cases two or more
291+
type variables and function parameters may be required
292+
293+
This leads to a very difficult combinatorial explosion because now we
294+
can fixate one type variable whilst leaving the other open. Multiple extensions
295+
of one type can be used with multiple extensions of another.
296+
297+
In Felix, the type system terms in the compiler have at least 6 auxilliary types
298+
for which multiple flat extensions cold be provided leading to an unmanagably
299+
large potential set of closed terms. of course this is <em>not</em> a problem
300+
with open recursion at all: it is a simple fact, and a blessing that open recursion
301+
provides the machine to represent these choices. The existence of fixpoint combinators
302+
are then crucial because fixation closures are anonymous and can be provided as required
303+
"on the fly".
304+
305+
Nevertheless, using this technology in complex applications whilst the very environment
306+
where the modularity provided is of most benefit, is also the environment where
307+
it is hardest to get right. In the Felix compiler I found it so difficult,
308+
dynamic typing was prefered; that is, handling terms that should have been
309+
erased by throwing an exception was a lot easier than constructing a type
310+
for every use case so as to obtain a compile time error.
311+
312+
I think it remains to develop language constructions that can leverage
313+
this technology in a more manageable way.
249314

0 commit comments

Comments
 (0)