@fraczak/k 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/TYPING.md ADDED
@@ -0,0 +1,368 @@
1
+ # Annotating subexpressions with type patterns
2
+
3
+ ## Input
4
+
5
+ As input we get AST of a program in terms of a set of function definitions,
6
+ i.e., mappings from function names to expressions. Expressions are trees defined
7
+ by the following grammar:
8
+
9
+ ```bfn-like
10
+ Expression := variable -- reference to a function (has to be defined in AST or built-in)
11
+ | COMP ( Expression, ..., Expression ) -- functional composition
12
+ | type -- identity function forcing type of its input and output
13
+ | PROD { Expression label, ..., Expression label } -- product
14
+ | MERGE < Expression, ..., Expression > -- union (merge)
15
+ | DOT label -- projection
16
+ -- extended with
17
+ | VECT [ Expression, ..., Expression ] -- vector
18
+ -- extended with EXPERIMENTAL list-comprehension
19
+ | CARET Expression -- list-comprehension aggregation
20
+ | PIPE -- unboxing for list-comprehension (only under CARET)
21
+ ```
22
+
23
+ ## Output
24
+
25
+ The output consists of the input AST with all of its nodes annotated with pairs of "type patterns".
26
+ Those type patterns define sets of types. Those tye patterns are represented by nodes of the
27
+ "type pattern graph" produced by the algorithm.
28
+ Ituitively, "type pattern graph" represents correlation between different subexpressions
29
+ in the input AST in terms of their input and output types.
30
+
31
+ More formally, a "type pattern graph" is a directed graph with nodes representing those
32
+ "type patterns" and edges, labeled by _projection labels_, representing
33
+ correlations between them.
34
+
35
+ Type pattern, i.e., node in the type pattern graph, is a value of the follwowing type:
36
+
37
+ ```k
38
+ $typePattern = < string code, open? product, open? union, {} vector, open? unknown >;
39
+ $open? = <{} open, {} closed >;
40
+ ```
41
+
42
+ The fields (from union and product types) are represented by edges coming out of the node.
43
+ The edges are labeled by the corresponding labels of the product or union types.
44
+ Except for the vector type, which is represented by a unique `vector-member` edge
45
+ pointing to the type pattern of the vector memebers.
46
+ Flag `open?` indicates if a given type can have more fields than the fields
47
+ represented by the outgoing edges.
48
+ Variant `unknown` is used to represent an arbitrary type.
49
+ Still, the fields corresponding to the out-going edges must exist in the type.
50
+
51
+ The output is a mapping from the nodes of the input AST to the nodes of the type pattern graph.
52
+
53
+ ## Algorithm
54
+
55
+ For every node of the input AST, we define a pair of type patterns.
56
+ Intuitively, such a pair, `(in,out)`, represents the
57
+ type of the partial function defined by the subtree rooted at the given node.
58
+
59
+ Let `L` be the list of all those type patterns, i.e., a list of the length
60
+ twice the size of the input AST (mesured in number of nodes).
61
+ All patterns in `L` are set to `{{open open?} unknown}`.
62
+ The list `L` is used to build the type pattern graph by constructing equivalence
63
+ classes of the type patterns.
64
+ The representation of equivalence classes is slightly different from
65
+ the standard disjoint-set data structure, however
66
+ we have the same operaitions `find` and `union` on the type patterns.
67
+ The difference is that we never modify already exising type patterns, e.g.,
68
+ the union (or even just a modification) generates a new type pattern node, so that
69
+ the history of unions and modifications of type patterns is preserved.
70
+ The equvalence classes, the root nodes of the type patterns forest, are going to be
71
+ the nodes of the type pattern graph.
72
+
73
+
74
+ ### Rules for type pattern derivation
75
+
76
+ #### Rule 1: Variable occurrence -- `ref`
77
+
78
+ In an expression, a variable is a reference to a function (build-in or defined in the AST).
79
+ The input and output type patterns of the variable expression have to match the type
80
+ patterns of the function.
81
+ Since the function can be used in many contexts, the type patterns of the function are
82
+ not modified by the expression using the variable.
83
+ Let `v` is an occurrence of the variable, i.e., a node in the AST, and `def(v)` is the definition, i.e.,
84
+ the root node of the AST subtree defining the function (or built-in) referenced by `v`.
85
+
86
+ ```pseudo
87
+ let (in_v, out_v) = annotation(v)
88
+ let (in_def, out_def) = annotation(def(v))
89
+ let cloned = clone([in_def, out_def])
90
+ let rep_in_v = find(in_v)
91
+ let rep_out_v = find(out_v)
92
+ let rep_in_def = cloned[find(in_def)]
93
+ let rep_out_def = cloned[find(out_def)]
94
+
95
+ UNIFY(rep_in_v, rep_in_def) BECAUSE "ref: unified with $rep_in_def"
96
+ UNIFY(rep_out_v, rep_out_def) BECAUSE "ref: unified with $rep_out_def"
97
+ ```
98
+
99
+ The procedure `UNIFY` will be difined later.
100
+
101
+ #### Rule 2: Functional composition -- `comp`
102
+
103
+ The input and output type patterns of the composition, `e = COMP(e1, ..., en)`, have to match
104
+ the input type pattern of `e1` and the output type pattern of `en`, respectively.
105
+ In addition, the output type pattern of `ei` has to match the input type pattern of `ei+1`.
106
+
107
+ ```pseudo
108
+ let (in_e, out_e) = annotation(e)
109
+ let (in_e1, out_e1) = annotation(e1)
110
+ ...
111
+ let (in_en, out_en) = annotation(en)
112
+ let rep_in_e = find(in_e)
113
+ let rep_out_e = find(out_e)
114
+ let rep_in_e1 = find(in_e1)
115
+ let rep_out_e1 = find(out_e1)
116
+ ...
117
+ let rep_in_en = find(in_en)
118
+ let rep_out_en = find(out_en)
119
+
120
+ UNIFY(rep_in_e, rep_in_e1) BECAUSE "comp: start"
121
+ UNIFY(rep_out_e, rep_out_en) BECAUSE "comp: end"
122
+
123
+ for i = 2 to n:
124
+ let rep_in_ei = find(in_ei)
125
+ let rep_out_ei = find(out_ei)
126
+ UNIFY(rep_out_ei-1, rep_in_ei) BECAUSE "comp: chaining"
127
+ ```
128
+
129
+ #### Rule 3: Type -- `type`
130
+
131
+ The input and output type patterns of a type expression, `type`, are the same,
132
+ namely `{type code}`, where `type` is the canonical name for the type.
133
+
134
+ ```pseudo
135
+ let (in_type, out_type) = annotation(type)
136
+ let rep_in_type = find(in_type)
137
+ let rep_out_type = find(out_type)
138
+ let type_node = CREATE_OR_FIND_TYPE_PATTERN_GRAPH_NODE_FOR_TYPE(type)
139
+ UNIFY (type_node, rep_in_type ,rep_out_type) BECAUSE "type"
140
+ ```
141
+
142
+ #### Rule 3: Product -- `prod` and `variant`
143
+
144
+ Let `p = PROD{e1 l1, ..., en ln}` be a product expression, i.e., n != 1.
145
+ There are two rules, one saying that input types of all inner
146
+ expressions must be the same and it is the input type of `p`.
147
+ The second rule says, that the output type is a product with
148
+ fiels `l1, ..., ln` of types corresponding to the output
149
+ types of `e1,..., en`, respectively.
150
+
151
+ ```pseudo
152
+ let (in_p, out_p) = annotation(p)
153
+ let rep_in_p = find(in_p)
154
+ let rep_out_p = find(out_p)
155
+ for i = 1 to n
156
+ let (in_ei,out_ei) = annotation(ei)
157
+ let rep_in_ei = find(in_ei)
158
+ let rep_out_ei = find(out_ei)
159
+ UNIFY(rep_in_p, rep_in_e1, ..., rep_in_en) BECAUSE "prod: input"
160
+
161
+ IF : CHECK IF WE SHOULD DO IT BY CHECKING IF rep_out_p IS ALREADY
162
+ : a closed product and has the edges going where they should
163
+ let aux_out_p = CREATE_TYPE_PATTERN_GRAPH_NODE({}) WITH_EDGES
164
+ TO rep_out_e1 BY l1,
165
+ ...,
166
+ rep_out_en BY ln
167
+ UNIFY(aux_out_p, rep_out_p) BECAUSE "prod: output"
168
+ ```
169
+
170
+ If `p = {e1 l1}`, the result will be of variant type, with at least one variant
171
+ label `l1` and of type corresponding to output type of `e1`.
172
+
173
+ ```pseudo
174
+ let (in_p, out_p) = annotation(p)
175
+ let rep_in_p = find(in_p)
176
+ let rep_out_p = find(out_p)
177
+ let (in_e1,out_e1) = annotation(e1)
178
+ let rep_in_e1 = find(in_e1)
179
+ let_rep_out_e1 = find(out_e1)
180
+ UNIFY(rep_in_p, rep_in_e1) BECAUSE "variant: input"
181
+
182
+ IF : CHECK IF WE SHOULD DO IT BY CHECKING IF rep_out_p IS ALREADY
183
+ : an open union and has an edge labeled by l1 going to rep_out_e1
184
+ let aux_out_p = CREATE_TYPE_PATTERN_GRAPH_NODE(<...>) WITH_EDGES
185
+ TO rep_out_e1 BY l1
186
+ UNIFY(aux_out_p, rep_out_p) BECAUSE "variant: output"
187
+ ```
188
+
189
+ #### Rule 4: Merge -- 'merge'
190
+
191
+ Let `u = MERGE<e1, ..., en>` be a merge expression. There is one rule,
192
+ all pairs of types for `u, e1, ..., en` have to be the same.
193
+
194
+ ```pseudo
195
+ let (in_u, out_u) = annotation(u)
196
+ let rep_in_u = find(in_u)
197
+ let rep_out_u = find(out_u)
198
+ for i = 1 to n
199
+ let (in_ei,out_ei) = annotation(ei)
200
+ let rep_in_ei = find(in_ei)
201
+ let_rep_out_ei = find(out_ei)
202
+ UNIFY(rep_in_u, rep_in_e1, ..., rep_in_en) BECAUSE "merge:input"
203
+ UNIFY(rep_out_u, rep_out_e1, ..., rep_out_en) BECAUSE "merge:output"
204
+ ```
205
+
206
+ #### Rule 5: Projection - `dot`
207
+
208
+ Let `d = DOT(l)` be a projection expression. The input type will have to have a field `l`.
209
+ The output type is the type of the field `l`.
210
+
211
+ WARNING: .3, .true. ."bla", etc... may be used with built-in types!!!
212
+
213
+ ```pseudo
214
+ let (in_d, out_d) = annatation(d)
215
+ let rep_in_d = find(in_d)
216
+ let rep_out_d = find(out_d)
217
+
218
+ IF : CHECK IF WE SHOULD DO IT BY CHECKING IF rep_in_d
219
+ : has already an edge labeled by l going to rep_out_d
220
+ let new_in_d = CREATE_TYPE_PATTERN_GRAPH_NODE((...)) WITH_EDGES
221
+ TO rep_out_d BY l
222
+ UNIFY(aux_in_d, rep_in_d) BECAUSE "dot"
223
+ ```
224
+
225
+ **Special cases are needed** for `vector` and built-in string, int, bool
226
+
227
+ #### Rule 6: Vector - `vector`
228
+
229
+ If `v = VECT[e1,...,en]` is a vector expression, then the input type for
230
+ `v, e1, ..., en` is the same, and the output type for all `e1, ..., en`
231
+ is the same, say `t`.
232
+ The output type for `v` is `[t]`.
233
+
234
+ ```pseudo
235
+ let (in_v, out_v) = annotation(v)
236
+ let rep_in_v = find(in_v)
237
+ let rep_out_v = find(out_v)
238
+ for i = 1 to n
239
+ let (in_ei,out_ei) = annotation(ei)
240
+ let rep_in_ei = find(in_ei)
241
+ let_rep_out_ei = find(out_ei)
242
+ UNIFY(rep_in_v, rep_in_e1, ..., rep_in_en) BECAUSE "vector: input"
243
+
244
+ IF : CHECK IF WE SHOULD DO IT BY CHECKING IF rep_out_v is a vector
245
+ : with edge labeled by "vector-member" going to rep_in_v
246
+ let new_out_v = CREATE_TYPE_PATTERN_GRAPH_NODE([]) WITH_EDGES
247
+ TO rep_in_v d BY 'vector-member'
248
+ UNIFY(new_out_v, rep_out_v) BECAUSE "vector: output"
249
+ ```
250
+
251
+ #### Rule 7: Caret - `caret`
252
+
253
+ Let `c = CARET(e)` be a list comprehension aggregation.
254
+ The input type is the same as the input type of `e`.
255
+ The output type of `c` is the vector of the output type of `e`.
256
+
257
+ ```pseudo
258
+ let (in_c, out_c) = annotation(c)
259
+ let rep_in_c = find(in_c)
260
+ let rep_out_c = find(out_c)
261
+ let (in_e, out_e) = annotation(e)
262
+ let rep_in_e = find(in_e)
263
+ let rep_out_e = find(out_e)
264
+ UNIFY(rep_in_c, rep_in_e) BECAUSE "caret: input"
265
+
266
+ IF : CHECK IF WE SHOULD DO IT BY CHECKING IF rep_out_c is a vector
267
+ : with edge labeled by "vector-member" going to rep_out_e
268
+ let aux_out_c = CREATE_TYPE_PATTERN_GRAPH_NODE([]) WITH_EDGES
269
+ TO rep_out_e BY "vector_member"
270
+ UNIFY(aux_out_c, rep_out_c) BECAUSE "caret: output"
271
+ ```
272
+
273
+ #### Rule 8: Pipe - `pipe`
274
+
275
+ Let `p = PIPE` be an unboxing operator for list comprehension.
276
+ The input type is a vector type.
277
+ The output type is the type of the vector members.
278
+
279
+ ```pseudo
280
+ let (in_p, out_p) = annotation(p)
281
+ let rep_in_p = find(in_p)
282
+ let rep_out_p = find(out_p)
283
+
284
+ IF : CHECK IF WE SHOULD DO IT BY CHECKING IF rep_in_p is a vector
285
+ : with edge labeled by "vector-member" going to rep_out_p
286
+ let aux_in_p = CREATE_TYPE_PATTERN_GRAPH_NODE([]) WITH_EDGES
287
+ TO rep_out_p BY "vector_member"
288
+ UNIFY(aux_in_p, rep_in_p) BECAUSE "pipe"
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Helper functions
294
+
295
+ There are two data-structures:
296
+
297
+ 1. `typePatternForest` -- the leaves of the forest are the type patterns of
298
+ the input AST nodes. Inner nodes are the type patterns generated by the rules.
299
+
300
+ 2. `typePatternGraph` -- the nodes are the roots of the typePatternForest.
301
+ The edges are labeled by the projection labels plus "vector_member", and
302
+ in general can lead to many nodes (represented as a list w/o repetition).
303
+
304
+ ### `UNIFY _, _, ...`
305
+
306
+ The procedure `UNIFY` takes as parameter a list of `typePatternGraph` nodes which all have to represent the same pattern.
307
+ The procedure modifies both, the `typePatternForest` and the `typePatternGraph`.
308
+
309
+ Also, the procedure may Fail if the nodes are not unifiable.
310
+
311
+ The recursive procedure `UNIFY(n1,...,nn)` is defined as follows:
312
+
313
+ ```pseudo
314
+ INPUT: n1,...,nn
315
+ let rep_n1,...,rep_nn = find(n1), ..., find(nn)
316
+ if rep_n1 = ... = rep_nn RETURN
317
+ let new_rep = flat-unify(rep_n1, ..., rep_nn)
318
+ ADD new_rep AS_REPRESENTATIVE_FOR rep_n1,..., rep_nn BY_RULE "unify"
319
+ MIGRADE_EDGES_FROM rep_n1,..., rep_nn TO new_rep
320
+ forech edge=(label,{x1,...xk}) FROM new-rep
321
+ CALL UNIFY(x1,...,xk)
322
+ ```
323
+
324
+ Notice that UNIFY is no-op when called with none or one node.
325
+
326
+ #### `MIGRADE_EDGES_FROM _, _, ... TO _`
327
+
328
+ Just copy the edges from the source nodes to the target node by combining destinations of the edges by the same label into a set (list without repetition).
329
+
330
+ #### `flat-unify _, _, ...`
331
+
332
+ `Flat-unify` is defined as reduce over the list of nodes, with initial value being `(...)`.
333
+ The binary reduce function is defined as follows (it is commutative):
334
+
335
+ | f-unify | (...) | {...} | <...> | () | [] | {} | <> | type
336
+ |----------|-------|-------|-------|------|------|------|------|------
337
+ |**type** | type | type | type | type | type | type | type | type
338
+ |**<>** | <> | ERROR | <> | <> | ERROR| ERROR| <>
339
+ |**{}** | {} | {} | ERROR | {} | [] | {}
340
+ |**[]** | [] | [] | ERROR | [] | []
341
+ |**()** | () | {} | <> | ()
342
+ |**<...>** | <...> | ERROR | <...>
343
+ |**{...}** | {...} | {...}
344
+ |**(...)** | (...)
345
+
346
+
347
+ ### `ADD _ AS_REPRESENTATIVE_FOR _, _, ... BY_RULE _`
348
+
349
+ Create a new type pattern node as a parrent in `typePatternForest` of other nodes.
350
+ A comment explaining how the node was created is added as well.
351
+
352
+ ### `ADD_EDGES_FROM _ TO _, _, ... BY _`
353
+
354
+ As the name indicates, adds an edge which can lead to many nodes.
355
+
356
+
357
+
358
+
359
+ Repeat the procedure until the type pattern graph is not changing anymore.
360
+
361
+ # TODO:
362
+
363
+ - [X] Redefine built-in CONS and SNOC to allign with no support for non-homogeneous lists
364
+ - [ ] Introduce a bound for typing
365
+ - [ ] Check for loops, e.g., `$x = < x f, ...>`, `$x = [x]`, `$x = {x f, ...}` -- this
366
+ looks a little arbitrary, but maybe this is actually an important part
367
+ - [ ] Integrate filters into typePatters (executions?)
368
+ - [ ] better error repporting - the info exists in the pattern parent tree