OSDN Git Service

Type checking, initial work.
[joypy/Thun.git] / docs / Types.md
1
2 # Type Inference
3
4 This notebook presents a simple type inferencer for Joy code.  It can infer the stack effect of most Joy expressions.  It built largely by means of existing ideas and research (some of it may be original but I'm not able to say, as I don't fully understand the all of the source material in the depth required to make that call.)  A great overview of the existing knowledge is a talk ["Type Inference in Stack-Based Programming Languages"](http://prl.ccs.neu.edu/blog/2017/03/10/type-inference-in-stack-based-programming-languages/) given by Rob Kleffner on or about 2017-03-10 as part of a course on the history of programming languages.
5
6 The notebook starts with a simple inferencer based on the work of Jaanus Pöial which we then progressively elaborate to cover more Joy semantics.  Along the way we write a simple "compiler" that emits Python code for what I like to call Yin functions.
7
8 Yin functions are those that only rearrange values in stacks, as opposed to Yang functions that actually work on the values themselves.  It's interesting to note that a Joy with *only* stacks (no other kinds of values) can be made and is Turing-complete, therefore all Yang functions are actually Yin functions, and all computation can be performed by manipulations of structures of containers, which is a restatement of the Laws of Form.  (Also, this implies that every program can be put into a form such that it can be computed in a single step, although that step may be enormous or unending.)
9
10 Although I haven't completed it yet, a Joy based on Laws of Form provides the foundation for a provably correct computing system "down to the metal".  This is my original and still primary motivation for developing Joy.  (I want a proven-correct Operating System for a swarm of trash-collecting recycler robots.  To trust it I have to implementment it myself from first principles, and I'm not smart enough to truly grok the existing literature and software, so I had to go look for and find LoF and Joy.  Now that I have the mental tools to build my robot OS I can get down to it.
11
12 Anyhow, here's type inference...
13
14 ## Part I: Pöial's Rules
15
16 ["Typing Tools for Typeless Stack Languages" by Jaanus Pöial
17 ](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.212.6026)
18
19     @INPROCEEDINGS{Pöial06typingtools,
20         author = {Jaanus Pöial},
21         title = {Typing tools for typeless stack languages},
22         booktitle = {In 23rd Euro-Forth Conference},
23         year = {2006},
24         pages = {40--46}
25     }
26
27 ### First Rule
28 This rule deals with functions (and literals) that put items on the stack `(-- d)`:
29
30
31        (a -- b)∘(-- d)
32     ---------------------
33          (a -- b d)
34
35 ### Second Rule
36 This rule deals with functions that consume items from the stack `(a --)`:
37
38        (a --)∘(c -- d)
39     ---------------------
40          (c a -- d)
41
42 ### Third Rule
43 The third rule is actually two rules.  These two rules deal with composing functions when the second one will consume one of items the first one produces.  The two types must be [*unified*](https://en.wikipedia.org/wiki/Robinson's_unification_algorithm) or a type conflict declared.
44
45        (a -- b t[i])∘(c u[j] -- d)   t <= u (t is subtype of u)
46     -------------------------------
47        (a -- b     )∘(c      -- d)   t[i] == t[k] == u[j]
48                                              ^
49
50        (a -- b t[i])∘(c u[j] -- d)   u <= t (u is subtype of t)
51     -------------------------------
52        (a -- b     )∘(c      -- d)   t[i] == u[k] == u[j]
53
54 Let's work through some examples by hand to develop an intuition for the algorithm.
55
56 There's a function in one of the other notebooks.
57
58     F == pop swap roll< rest rest cons cons
59
60 It's all "stack chatter" and list manipulation so we should be able to deduce its type.
61
62 ### Stack Effect Comments
63 Joy function types will be represented by Forth-style stack effect comments.  I'm going to use numbers instead of names to keep track of the stack arguments.  (A little bit like [De Bruijn index](https://en.wikipedia.org/wiki/De_Bruijn_index), at least it reminds me of them):
64
65     pop (1 --)
66
67     swap (1 2 -- 2 1)
68
69     roll< (1 2 3 -- 2 3 1)
70
71 These commands alter the stack but don't "look at" the values so these numbers represent an "Any type".
72
73 ### `pop swap`
74
75     (1 --) (1 2 -- 2 1)
76     
77 Here we encounter a complication. The argument numbers need to be made unique among both sides.   For this let's change `pop` to use 0:
78
79     (0 --) (1 2 -- 2 1)
80
81 Following the second rule:
82     
83     (1 2 0 -- 2 1)
84
85 ### `pop∘swap roll<`
86
87     (1 2 0 -- 2 1) (1 2 3 -- 2 3 1)
88
89 Let's re-label them:
90
91     (1a 2a 0a -- 2a 1a) (1b 2b 3b -- 2b 3b 1b)
92
93 Now we follow the rules.
94
95 We must unify `1a` and `3b`, and `2a` and `2b`, replacing the terms in the forms:
96
97     (1a 2a 0a -- 2a 1a) (1b 2b 3b -- 2b 3b 1b)
98                                                 w/  {1a: 3b}
99     (3b 2a 0a -- 2a   ) (1b 2b    -- 2b 3b 1b)
100                                                 w/  {2a: 2b}
101     (3b 2b 0a --      ) (1b       -- 2b 3b 1b)
102
103 Here we must apply the second rule:
104
105        (3b 2b 0a --) (1b -- 2b 3b 1b)
106     -----------------------------------
107          (1b 3b 2b 0a -- 2b 3b 1b)
108
109 Now we de-label the type, uh, labels:
110
111     (1b 3b 2b 0a -- 2b 3b 1b)
112
113     w/ {
114         1b: 1,
115         3b: 2,
116         2b: 3,
117         0a: 0,
118         }
119
120     (1 2 3 0 -- 3 2 1)
121
122 And now we have the stack effect comment for `pop∘swap∘roll<`.
123
124 ### Compiling `pop∘swap∘roll<`
125 The simplest way to "compile" this function would be something like:
126
127
128 ```python
129 def poswrd(s, e, d):
130     return rolldown(*swap(*pop(s, e, d)))
131 ```
132
133 However, internally this function would still be allocating tuples (stack cells) and doing other unnecesssary work.
134
135 Looking ahead for a moment, from the stack effect comment:
136
137     (1 2 3 0 -- 3 2 1)
138
139 We should be able to directly write out a Python function like:
140
141
142 ```python
143 def poswrd(stack):
144     (_, (a, (b, (c, stack)))) = stack
145     return (c, (b, (a, stack)))
146 ```
147
148 This eliminates the internal work of the first version.  Because this function only rearranges the stack and doesn't do any actual processing on the stack items themselves all the information needed to implement it is in the stack effect comment.
149
150 ### Functions on Lists
151 These are slightly tricky.
152
153     rest ( [1 ...] -- [...] )
154
155     cons ( 1 [...] -- [1 ...] )
156
157 ### `pop∘swap∘roll< rest`
158
159     (1 2 3 0 -- 3 2 1) ([1 ...] -- [...])
160
161 Re-label (instead of adding left and right tags I'm just taking the next available index number for the right-side stack effect comment):
162
163     (1 2 3 0 -- 3 2 1) ([4 ...] -- [...])
164
165 Unify and update:
166
167     (1       2 3 0 -- 3 2 1) ([4 ...] -- [...])
168                                                  w/ {1: [4 ...]}
169     ([4 ...] 2 3 0 -- 3 2  ) (        -- [...])
170
171 Apply the first rule:
172
173        ([4 ...] 2 3 0 -- 3 2) (-- [...])
174     ---------------------------------------
175          ([4 ...] 2 3 0 -- 3 2 [...])
176
177 And there we are.
178
179 ### `pop∘swap∘roll<∘rest rest`
180
181 Let's do it again.
182
183     ([4 ...] 2 3 0 -- 3 2 [...]) ([1 ...] -- [...])
184
185 Re-label (the tails of the lists on each side each get their own label):
186
187     ([4 .0.] 2 3 0 -- 3 2 [.0.]) ([5 .1.] -- [.1.])
188
189 Unify and update (note the opening square brackets have been omited in the substitution dict, this is deliberate and I'll explain below):
190
191     ([4 .0.]   2 3 0 -- 3 2 [.0.]  ) ([5 .1.] -- [.1.])
192                                                         w/ { .0.] : 5 .1.] }
193     ([4 5 .1.] 2 3 0 -- 3 2 [5 .1.]) ([5 .1.] -- [.1.])
194
195 How do we find `.0.]` in `[4 .0.]` and replace it with `5 .1.]` getting the result `[4 5 .1.]`?  This might seem hard, but because the underlying structure of the Joy list is a cons-list in Python it's actually pretty easy.  I'll explain below.
196
197 Next we unify and find our two terms are the same already: `[5 .1.]`:
198
199     ([4 5 .1.] 2 3 0 -- 3 2 [5 .1.]) ([5 .1.] -- [.1.])
200
201 Giving us:
202
203     ([4 5 .1.] 2 3 0 -- 3 2) (-- [.1.])
204
205 From here we apply the first rule and get:
206
207     ([4 5 .1.] 2 3 0 -- 3 2 [.1.])
208
209 Cleaning up the labels:
210
211     ([4 5 ...] 2 3 1 -- 3 2 [...])
212
213 This is the stack effect of `pop∘swap∘roll<∘rest∘rest`.
214
215 ### `pop∘swap∘roll<∘rest∘rest cons`
216
217     ([4 5 ...] 2 3 1 -- 3 2 [...]) (1 [...] -- [1 ...])
218
219 Re-label:
220
221     ([4 5 .1.] 2 3 1 -- 3 2 [.1.]) (6 [.2.] -- [6 .2.])
222
223 Unify:
224
225     ([4 5 .1.] 2 3 1 -- 3 2 [.1.]) (6 [.2.] -- [6 .2.])
226                                                          w/ { .1.] : .2.] }
227     ([4 5 .2.] 2 3 1 -- 3 2      ) (6       -- [6 .2.])
228                                                          w/ {2: 6}
229     ([4 5 .2.] 6 3 1 -- 3        ) (        -- [6 .2.])
230
231 First rule:
232
233     ([4 5 .2.] 6 3 1 -- 3 [6 .2.])
234
235 Re-label:
236
237     ([4 5 ...] 2 3 1 -- 3 [2 ...])
238
239 Done.
240
241 ### `pop∘swap∘roll<∘rest∘rest∘cons cons`
242 One more time.
243
244     ([4 5 ...] 2 3 1 -- 3 [2 ...]) (1 [...] -- [1 ...])
245
246 Re-label:
247
248     ([4 5 .1.] 2 3 1 -- 3 [2 .1.]) (6 [.2.] -- [6 .2.])
249
250 Unify:
251
252     ([4 5 .1.] 2 3 1 -- 3 [2 .1.]) (6 [.2.] -- [6 .2.]  )
253                                                            w/ { .2.] : 2 .1.] }
254     ([4 5 .1.] 2 3 1 -- 3        ) (6       -- [6 2 .1.])
255                                                            w/ {3: 6}
256     ([4 5 .1.] 2 6 1 --          ) (        -- [6 2 .1.])
257
258 First or second rule:
259
260     ([4 5 .1.] 2 6 1 -- [6 2 .1.])
261
262 Clean up the labels:
263
264     ([4 5 ...] 2 3 1 -- [3 2 ...])
265
266 And there you have it, the stack effect for `pop∘swap∘roll<∘rest∘rest∘cons∘cons`.
267
268     ([4 5 ...] 2 3 1 -- [3 2 ...])
269
270 From this stack effect comment it should be possible to construct the following Python code:
271
272
273 ```python
274 def F(stack):
275     (_, (d, (c, ((a, (b, S0)), stack)))) = stack
276     return (d, (c, S0)), stack
277 ```
278
279 ## Part II: Implementation
280
281 ### Representing Stack Effect Comments in Python
282
283 I'm going to use pairs of tuples of type descriptors, which will be integers or tuples of type descriptors:
284
285
286 ```python
287 roll_dn = (1, 2, 3), (2, 3, 1)
288
289 pop = (1,), ()
290
291 swap = (1, 2), (2, 1)
292 ```
293
294 ### `compose()`
295
296
297 ```python
298 def compose(f, g):
299
300     (f_in, f_out), (g_in, g_out) = f, g
301
302     # First rule.
303     #
304     #       (a -- b) (-- d)
305     #    ---------------------
306     #         (a -- b d)
307
308     if not g_in:
309
310         fg_in, fg_out = f_in, f_out + g_out
311
312     # Second rule.
313     #
314     #       (a --) (c -- d)
315     #    ---------------------
316     #         (c a -- d)
317
318     elif not f_out:
319
320         fg_in, fg_out = g_in + f_in, g_out
321
322     else: # Unify, update, recur.
323
324         fo, gi = f_out[-1], g_in[-1]
325
326         s = unify(gi, fo)
327
328         if s == False:  # s can also be the empty dict, which is ok.
329             raise TypeError('Cannot unify %r and %r.' % (fo, gi))
330
331         f_g = (f_in, f_out[:-1]), (g_in[:-1], g_out)
332
333         if s: f_g = update(s, f_g)
334
335         fg_in, fg_out = compose(*f_g)
336
337     return fg_in, fg_out
338 ```
339
340 ### `unify()`
341
342
343 ```python
344 def unify(u, v, s=None):
345     if s is None:
346         s = {}
347
348     if u == v:
349         return s
350
351     if isinstance(u, int):
352         s[u] = v
353         return s
354
355     if isinstance(v, int):
356         s[v] = u
357         return s
358
359     return False
360 ```
361
362 ### `update()`
363
364
365 ```python
366 def update(s, term):
367     if not isinstance(term, tuple):
368         return s.get(term, term)
369     return tuple(update(s, inner) for inner in term)
370 ```
371
372 ### `relabel()`
373
374
375 ```python
376 def relabel(left, right):
377     return left, _1000(right)
378
379 def _1000(right):
380     if not isinstance(right, tuple):
381         return 1000 + right
382     return tuple(_1000(n) for n in right)
383
384 relabel(pop, swap)
385 ```
386
387
388
389
390     (((1,), ()), ((1001, 1002), (1002, 1001)))
391
392
393
394 ### `delabel()`
395
396
397 ```python
398 def delabel(f):
399     s = {u: i for i, u in enumerate(sorted(_unique(f)))}
400     return update(s, f)
401
402 def _unique(f, seen=None):
403     if seen is None:
404         seen = set()
405     if not isinstance(f, tuple):
406         seen.add(f)
407     else:
408         for inner in f:
409             _unique(inner, seen)
410     return seen
411
412 delabel(relabel(pop, swap))
413 ```
414
415
416
417
418     (((0,), ()), ((1, 2), (2, 1)))
419
420
421
422 ### `C()`
423
424 At last we put it all together in a function `C()` that accepts two stack effect comments and returns their composition (or raises and exception if they can't be composed due to type conflicts.)
425
426
427 ```python
428 def C(f, g):
429     f, g = relabel(f, g)
430     fg = compose(f, g)
431     return delabel(fg)
432 ```
433
434 Let's try it out.
435
436
437 ```python
438 C(pop, swap)
439 ```
440
441
442
443
444     ((1, 2, 0), (2, 1))
445
446
447
448
449 ```python
450 C(C(pop, swap), roll_dn)
451 ```
452
453
454
455
456     ((3, 1, 2, 0), (2, 1, 3))
457
458
459
460
461 ```python
462 C(swap, roll_dn)
463 ```
464
465
466
467
468     ((2, 0, 1), (1, 0, 2))
469
470
471
472
473 ```python
474 C(pop, C(swap, roll_dn))
475 ```
476
477
478
479
480     ((3, 1, 2, 0), (2, 1, 3))
481
482
483
484
485 ```python
486 poswrd = reduce(C, (pop, swap, roll_dn))
487 poswrd
488 ```
489
490
491
492
493     ((3, 1, 2, 0), (2, 1, 3))
494
495
496
497 ### Stack Functions
498 Here's that trick to represent functions like `rest` and `cons` that manipulate stacks.  We use a cons-list of tuples and give the tails their own numbers.  Then everything above already works. 
499
500
501 ```python
502 rest = ((1, 2),), (2,)
503
504 cons = (1, 2), ((1, 2),)
505 ```
506
507
508 ```python
509 C(poswrd, rest)
510 ```
511
512
513
514
515     (((3, 4), 1, 2, 0), (2, 1, 4))
516
517
518
519 Compare this to the stack effect comment we wrote above:
520
521     ((  (3, 4), 1, 2, 0 ), ( 2, 1,   4  ))
522     (   [4 ...] 2  3  0  --  3  2  [...])
523
524 The translation table, if you will, would be:
525
526     {
527     3: 4,
528     4: ...],
529     1: 2,
530     2: 3,
531     0: 0,
532     }
533
534
535 ```python
536 F = reduce(C, (pop, swap, roll_dn, rest, rest, cons, cons))
537
538 F
539 ```
540
541
542
543
544     (((3, (4, 5)), 1, 2, 0), ((2, (1, 5)),))
545
546
547
548 Compare with the stack effect comment and you can see it works fine:
549
550     ([4 5 ...] 2 3 1 -- [3 2 ...])
551       3 4  5   1 2 0     2 1  5
552
553 ### Dealing with `cons` and `uncons`
554 However, if we try to compose e.g. `cons` and `uncons` it won't work:
555
556
557 ```python
558 uncons = ((1, 2),), (1, 2)
559 ```
560
561
562 ```python
563 try:
564     C(cons, uncons)
565 except Exception, e:
566     print e
567 ```
568
569     Cannot unify (1, 2) and (1001, 1002).
570
571
572 #### `unify()` version 2
573 The problem is that the `unify()` function as written doesn't handle the case when both terms are tuples.  We just have to add a clause to deal with this recursively:
574
575
576 ```python
577 def unify(u, v, s=None):
578     if s is None:
579         s = {}
580     elif s:
581         u = update(s, u)
582         v = update(s, v)
583
584     if u == v:
585         return s
586
587     if isinstance(u, int):
588         s[u] = v
589         return s
590
591     if isinstance(v, int):
592         s[v] = u
593         return s
594
595     if isinstance(u, tuple) and isinstance(v, tuple):
596         if len(u) != len(v) != 2:
597             raise ValueError(repr((u, v)))
598         for uu, vv in zip(u, v):
599             s = unify(uu, vv, s)
600             if s == False: # (instead of a substitution dict.)
601                 break
602         return s
603  
604     return False
605 ```
606
607
608 ```python
609 C(cons, uncons)
610 ```
611
612
613
614
615     ((0, 1), (0, 1))
616
617
618
619 ## Part III: Compiling Yin Functions
620 Now consider the Python function we would like to derive:
621
622
623 ```python
624 def F_python(stack):
625     (_, (d, (c, ((a, (b, S0)), stack)))) = stack
626     return (d, (c, S0)), stack
627 ```
628
629 And compare it to the input stack effect comment tuple we just computed:
630
631
632 ```python
633 F[0]
634 ```
635
636
637
638
639     ((3, (4, 5)), 1, 2, 0)
640
641
642
643 The stack-de-structuring tuple has nearly the same form as our input stack effect comment tuple, just in the reverse order:
644
645     (_, (d, (c, ((a, (b, S0)), stack))))
646
647 Remove the punctuation:
648
649      _   d   c   (a, (b, S0))
650
651 Reverse the order and compare:
652
653      (a, (b, S0))   c   d   _
654     ((3, (4, 5 )),  1,  2,  0)
655
656 Eh?
657
658 And the return tuple 
659
660
661 ```python
662 F[1]
663 ```
664
665
666
667
668     ((2, (1, 5)),)
669
670
671
672 is similar to the output stack effect comment tuple:
673
674     ((d, (c, S0)), stack)
675     ((2, (1, 5 )),      )
676
677 This should make it pretty easy to write a Python function that accepts the stack effect comment tuples and returns a new Python function (either as a string of code or a function object ready to use) that performs the semantics of that Joy function (described by the stack effect.)
678
679 ### Python Identifiers
680 We want to substitute Python identifiers for the integers.  I'm going to repurpose `joy.parser.Symbol` class for this:
681
682
683 ```python
684 from collections import defaultdict
685 from joy.parser import Symbol
686
687
688 def _names_for():
689     I = iter(xrange(1000))
690     return lambda: Symbol('a%i' % next(I))
691
692
693 def identifiers(term, s=None):
694     if s is None:
695         s = defaultdict(_names_for())
696     if isinstance(term, int):
697         return s[term]
698     return tuple(identifiers(inner, s) for inner in term)
699 ```
700
701 ### `doc_from_stack_effect()`
702 As a convenience I've implemented a function to convert the Python stack effect comment tuples to reasonable text format.  There are some details in how this code works that related to stuff later in the notebook, so you should skip it for now and read it later if you're interested.
703
704
705 ```python
706 def doc_from_stack_effect(inputs, outputs):
707     return '(%s--%s)' % (
708         ' '.join(map(_to_str, inputs + ('',))),
709         ' '.join(map(_to_str, ('',) + outputs))
710     )
711
712
713 def _to_str(term):
714     if not isinstance(term, tuple):
715         try:
716             t = term.prefix == 's'
717         except AttributeError:
718             return str(term)
719         return '[.%i.]' % term.number if t else str(term)
720
721     a = []
722     while term and isinstance(term, tuple):
723         item, term = term
724         a.append(_to_str(item))
725
726     try:
727         n = term.number
728     except AttributeError:
729         n = term
730     else:
731         if term.prefix != 's':
732             raise ValueError('Stack label: %s' % (term,))
733
734     a.append('.%s.' % (n,))
735     return '[%s]' % ' '.join(a)
736 ```
737
738 ### `compile_()`
739 Now we can write a compiler function to emit Python source code.  (The underscore suffix distiguishes it from the built-in `compile()` function.)
740
741
742 ```python
743 def compile_(name, f, doc=None):
744     if doc is None:
745         doc = doc_from_stack_effect(*f)
746     inputs, outputs = identifiers(f)
747     i = o = Symbol('stack')
748     for term in inputs:
749         i = term, i
750     for term in outputs:
751         o = term, o
752     return '''def %s(stack):
753     """%s"""
754     %s = stack
755     return %s''' % (name, doc, i, o)
756 ```
757
758 Here it is in action:
759
760
761 ```python
762 source = compile_('F', F)
763
764 print source
765 ```
766
767     def F(stack):
768         """([3 4 .5.] 1 2 0 -- [2 1 .5.])"""
769         (a5, (a4, (a3, ((a0, (a1, a2)), stack)))) = stack
770         return ((a4, (a3, a2)), stack)
771
772
773 Compare:
774
775
776 ```python
777 def F_python(stack):
778     (_, (d, (c, ((a, (b, S0)), stack)))) = stack
779     return ((d, (c, S0)), stack)
780 ```
781
782 Next steps:
783
784
785 ```python
786 L = {}
787
788 eval(compile(source, '__main__', 'single'), {}, L)
789
790 L['F']
791 ```
792
793
794
795
796     <function F>
797
798
799
800 Let's try it out:
801
802
803 ```python
804 from notebook_preamble import D, J, V
805 from joy.library import SimpleFunctionWrapper
806 ```
807
808
809 ```python
810 D['F'] = SimpleFunctionWrapper(L['F'])
811 ```
812
813
814 ```python
815 J('[4 5 ...] 2 3 1 F')
816 ```
817
818     [3 2 ...]
819
820
821 With this, we have a partial Joy compiler that works on the subset of Joy functions that manipulate stacks (both what I call "stack chatter" and the ones that manipulate stacks on the stack.)
822
823 I'm probably going to modify the definition wrapper code to detect definitions that can be compiled by this partial compiler and do it automatically.  It might be a reasonable idea to detect sequences of compilable functions in definitions that have uncompilable functions in them and just compile those.  However, if your library is well-factored this might be less helpful.
824
825 ### Compiling Library Functions
826 We can use `compile_()` to generate many primitives in the library from their stack effect comments:
827
828
829 ```python
830 def defs():
831
832     rolldown = (1, 2, 3), (2, 3, 1)
833
834     rollup = (1, 2, 3), (3, 1, 2)
835
836     pop = (1,), ()
837
838     swap = (1, 2), (2, 1)
839
840     rest = ((1, 2),), (2,)
841     
842     rrest = C(rest, rest)
843
844     cons = (1, 2), ((1, 2),)
845
846     uncons = ((1, 2),), (1, 2)
847     
848     swons = C(swap, cons)
849
850     return locals()
851 ```
852
853
854 ```python
855 for name, stack_effect_comment in sorted(defs().items()):
856     print
857     print compile_(name, stack_effect_comment)
858     print
859 ```
860
861     
862     def cons(stack):
863         """(1 2 -- [1 .2.])"""
864         (a1, (a0, stack)) = stack
865         return ((a0, a1), stack)
866     
867     
868     def pop(stack):
869         """(1 --)"""
870         (a0, stack) = stack
871         return stack
872     
873     
874     def rest(stack):
875         """([1 .2.] -- 2)"""
876         ((a0, a1), stack) = stack
877         return (a1, stack)
878     
879     
880     def rolldown(stack):
881         """(1 2 3 -- 2 3 1)"""
882         (a2, (a1, (a0, stack))) = stack
883         return (a0, (a2, (a1, stack)))
884     
885     
886     def rollup(stack):
887         """(1 2 3 -- 3 1 2)"""
888         (a2, (a1, (a0, stack))) = stack
889         return (a1, (a0, (a2, stack)))
890     
891     
892     def rrest(stack):
893         """([0 1 .2.] -- 2)"""
894         ((a0, (a1, a2)), stack) = stack
895         return (a2, stack)
896     
897     
898     def swap(stack):
899         """(1 2 -- 2 1)"""
900         (a1, (a0, stack)) = stack
901         return (a0, (a1, stack))
902     
903     
904     def swons(stack):
905         """(0 1 -- [1 .0.])"""
906         (a1, (a0, stack)) = stack
907         return ((a1, a0), stack)
908     
909     
910     def uncons(stack):
911         """([1 .2.] -- 1 2)"""
912         ((a0, a1), stack) = stack
913         return (a1, (a0, stack))
914     
915
916
917 ## Part IV: Types and Subtypes of Arguments
918 So far we have dealt with types of functions, those dealing with simple stack manipulation.  Let's extend our machinery to deal with types of arguments.
919
920 ### "Number" Type
921
922 Consider the definition of `sqr`:
923
924     sqr == dup mul
925
926
927 The `dup` function accepts one *anything* and returns two of that:
928
929     dup (1 -- 1 1)
930
931 And `mul` accepts two "numbers" (we're ignoring ints vs. floats vs. complex, etc., for now) and returns just one:
932
933     mul (n n -- n)
934
935 So we're composing:
936
937     (1 -- 1 1)∘(n n -- n)
938
939 The rules say we unify 1 with `n`:
940
941        (1 -- 1 1)∘(n n -- n)
942     ---------------------------  w/  {1: n}
943        (1 -- 1  )∘(n   -- n)
944
945 This involves detecting that "Any type" arguments can accept "numbers".  If we were composing these functions the other way round this is still the case:
946
947        (n n -- n)∘(1 -- 1 1)
948     ---------------------------  w/  {1: n}
949        (n n --  )∘(  -- n n) 
950
951 The important thing here is that the mapping is going the same way in both cases, from the "any" integer to the number
952
953 ### Distinguishing Numbers
954 We should also mind that the number that `mul` produces is not (necessarily) the same as either of its inputs, which are not (necessarily) the same as each other:
955
956     mul (n2 n1 -- n3)
957
958
959        (1  -- 1  1)∘(n2 n1 -- n3)
960     --------------------------------  w/  {1: n2}
961        (n2 -- n2  )∘(n2    -- n3)
962
963
964        (n2 n1 -- n3)∘(1 -- 1  1 )
965     --------------------------------  w/  {1: n3}
966        (n2 n1 --   )∘(  -- n3 n3) 
967
968
969
970 ### Distinguishing Types
971 So we need separate domains of "any" numbers and "number" numbers, and we need to be able to ask the order of these domains.  Now the notes on the right side of rule three make more sense, eh?
972
973        (a -- b t[i])∘(c u[j] -- d)   t <= u (t is subtype of u)
974     -------------------------------
975        (a -- b     )∘(c      -- d)   t[i] == t[k] == u[j]
976                                              ^
977
978        (a -- b t[i])∘(c u[j] -- d)   u <= t (u is subtype of t)
979     -------------------------------
980        (a -- b     )∘(c      -- d)   t[i] == u[k] == u[j]
981
982 The indices `i`, `k`, and `j` are the number part of our labels and `t` and `u` are the domains.
983
984 By creative use of Python's "double underscore" methods we can define a Python class hierarchy of Joy types and use the `issubclass()` method to establish domain ordering, as well as other handy behaviour that will make it fairly easy to reuse most of the code above.
985
986
987 ```python
988 class AnyJoyType(object):
989
990     prefix = 'a'
991
992     def __init__(self, number):
993         self.number = number
994
995     def __repr__(self):
996         return self.prefix + str(self.number)
997
998     def __eq__(self, other):
999         return (
1000             isinstance(other, self.__class__)
1001             and other.prefix == self.prefix
1002             and other.number == self.number
1003         )
1004
1005     def __ge__(self, other):
1006         return issubclass(other.__class__, self.__class__)
1007
1008     def __add__(self, other):
1009         return self.__class__(self.number + other)
1010     __radd__ = __add__
1011     
1012     def __hash__(self):
1013         return hash(repr(self))
1014
1015
1016 class NumberJoyType(AnyJoyType): prefix = 'n'
1017 class FloatJoyType(NumberJoyType): prefix = 'f'
1018 class IntJoyType(FloatJoyType): prefix = 'i'
1019
1020
1021 class StackJoyType(AnyJoyType):
1022     prefix = 's'
1023
1024
1025 _R = range(10)
1026 A = map(AnyJoyType, _R)
1027 N = map(NumberJoyType, _R)
1028 S = map(StackJoyType, _R)
1029 ```
1030
1031 Mess with it a little:
1032
1033
1034 ```python
1035 from itertools import permutations
1036 ```
1037
1038 "Any" types can be specialized to numbers and stacks, but not vice versa:
1039
1040
1041 ```python
1042 for a, b in permutations((A[0], N[0], S[0]), 2):
1043     print a, '>=', b, '->', a >= b
1044 ```
1045
1046     a0 >= n0 -> True
1047     a0 >= s0 -> True
1048     n0 >= a0 -> False
1049     n0 >= s0 -> False
1050     s0 >= a0 -> False
1051     s0 >= n0 -> False
1052
1053
1054 Our crude [Numerical Tower](https://en.wikipedia.org/wiki/Numerical_tower) of *numbers* > *floats* > *integers* works as well (but we're not going to use it yet):
1055
1056
1057 ```python
1058 for a, b in permutations((A[0], N[0], FloatJoyType(0), IntJoyType(0)), 2):
1059     print a, '>=', b, '->', a >= b
1060 ```
1061
1062     a0 >= n0 -> True
1063     a0 >= f0 -> True
1064     a0 >= i0 -> True
1065     n0 >= a0 -> False
1066     n0 >= f0 -> True
1067     n0 >= i0 -> True
1068     f0 >= a0 -> False
1069     f0 >= n0 -> False
1070     f0 >= i0 -> True
1071     i0 >= a0 -> False
1072     i0 >= n0 -> False
1073     i0 >= f0 -> False
1074
1075
1076 ### Typing `sqr`
1077
1078
1079 ```python
1080 dup = (A[1],), (A[1], A[1])
1081
1082 mul = (N[1], N[2]), (N[3],)
1083 ```
1084
1085
1086 ```python
1087 dup
1088 ```
1089
1090
1091
1092
1093     ((a1,), (a1, a1))
1094
1095
1096
1097
1098 ```python
1099 mul
1100 ```
1101
1102
1103
1104
1105     ((n1, n2), (n3,))
1106
1107
1108
1109 ### Modifying the Inferencer
1110 Re-labeling still works fine:
1111
1112
1113 ```python
1114 foo = relabel(dup, mul)
1115
1116 foo
1117 ```
1118
1119
1120
1121
1122     (((a1,), (a1, a1)), ((n1001, n1002), (n1003,)))
1123
1124
1125
1126 #### `delabel()` version 2
1127 The `delabel()` function needs an overhaul.  It now has to keep track of how many labels of each domain it has "seen".
1128
1129
1130 ```python
1131 from collections import Counter
1132
1133
1134 def delabel(f, seen=None, c=None):
1135     if seen is None:
1136         assert c is None
1137         seen, c = {}, Counter()
1138
1139     try:
1140         return seen[f]
1141     except KeyError:
1142         pass
1143
1144     if not isinstance(f, tuple):
1145         seen[f] = f.__class__(c[f.prefix])
1146         c[f.prefix] += 1
1147         return seen[f]
1148
1149     return tuple(delabel(inner, seen, c) for inner in f)
1150 ```
1151
1152
1153 ```python
1154 delabel(foo)
1155 ```
1156
1157
1158
1159
1160     (((a0,), (a0, a0)), ((n0, n1), (n2,)))
1161
1162
1163
1164 #### `unify()` version 3
1165
1166
1167 ```python
1168 def unify(u, v, s=None):
1169     if s is None:
1170         s = {}
1171     elif s:
1172         u = update(s, u)
1173         v = update(s, v)
1174
1175     if u == v:
1176         return s
1177
1178     if isinstance(u, AnyJoyType) and isinstance(v, AnyJoyType):
1179         if u >= v:
1180             s[u] = v
1181             return s
1182         if v >= u:
1183             s[v] = u
1184             return s
1185         raise TypeError('Cannot unify %r and %r.' % (u, v))
1186
1187     if isinstance(u, tuple) and isinstance(v, tuple):
1188         if len(u) != len(v) != 2:
1189             raise TypeError(repr((u, v)))
1190         for uu, vv in zip(u, v):
1191             s = unify(uu, vv, s)
1192             if s == False: # (instead of a substitution dict.)
1193                 break
1194         return s
1195  
1196     if isinstance(v, tuple):
1197         if not stacky(u):
1198             raise TypeError('Cannot unify %r and %r.' % (u, v))
1199         s[u] = v
1200         return s
1201
1202     if isinstance(u, tuple):
1203         if not stacky(v):
1204             raise TypeError('Cannot unify %r and %r.' % (v, u))
1205         s[v] = u
1206         return s
1207
1208     return False
1209
1210
1211 def stacky(thing):
1212     return thing.__class__ in {AnyJoyType, StackJoyType}
1213 ```
1214
1215 Rewrite the stack effect comments:
1216
1217
1218 ```python
1219 def defs():
1220
1221     rolldown = (A[1], A[2], A[3]), (A[2], A[3], A[1])
1222
1223     rollup = (A[1], A[2], A[3]), (A[3], A[1], A[2])
1224
1225     pop = (A[1],), ()
1226
1227     popop = (A[2], A[1],), ()
1228
1229     popd = (A[2], A[1],), (A[1],)
1230
1231     popdd = (A[3], A[2], A[1],), (A[2], A[1],)
1232
1233     swap = (A[1], A[2]), (A[2], A[1])
1234
1235     rest = ((A[1], S[1]),), (S[1],)
1236
1237     rrest = C(rest, rest)
1238
1239     cons = (A[1], S[1]), ((A[1], S[1]),)
1240
1241     ccons = C(cons, cons)
1242
1243     uncons = ((A[1], S[1]),), (A[1], S[1])
1244
1245     swons = C(swap, cons)
1246
1247     dup = (A[1],), (A[1], A[1])
1248
1249     dupd = (A[2], A[1]), (A[2], A[2], A[1])
1250
1251     mul = (N[1], N[2]), (N[3],)
1252     
1253     sqrt = C(dup, mul)
1254
1255     first = ((A[1], S[1]),), (A[1],)
1256
1257     second = C(rest, first)
1258
1259     third = C(rest, second)
1260
1261     tuck = (A[2], A[1]), (A[1], A[2], A[1])
1262
1263     over = (A[2], A[1]), (A[2], A[1], A[2])
1264     
1265     succ = pred = (N[1],), (N[2],)
1266     
1267     divmod_ = pm = (N[2], N[1]), (N[4], N[3])
1268
1269     return locals()
1270 ```
1271
1272
1273 ```python
1274 DEFS = defs()
1275 ```
1276
1277
1278 ```python
1279 for name, stack_effect_comment in sorted(DEFS.items()):
1280     print name, '=', doc_from_stack_effect(*stack_effect_comment)
1281 ```
1282
1283     ccons = (a0 a1 [.0.] -- [a0 a1 .0.])
1284     cons = (a1 [.1.] -- [a1 .1.])
1285     divmod_ = (n2 n1 -- n4 n3)
1286     dup = (a1 -- a1 a1)
1287     dupd = (a2 a1 -- a2 a2 a1)
1288     first = ([a1 .1.] -- a1)
1289     mul = (n1 n2 -- n3)
1290     over = (a2 a1 -- a2 a1 a2)
1291     pm = (n2 n1 -- n4 n3)
1292     pop = (a1 --)
1293     popd = (a2 a1 -- a1)
1294     popdd = (a3 a2 a1 -- a2 a1)
1295     popop = (a2 a1 --)
1296     pred = (n1 -- n2)
1297     rest = ([a1 .1.] -- [.1.])
1298     rolldown = (a1 a2 a3 -- a2 a3 a1)
1299     rollup = (a1 a2 a3 -- a3 a1 a2)
1300     rrest = ([a0 a1 .0.] -- [.0.])
1301     second = ([a0 a1 .0.] -- a1)
1302     sqrt = (n0 -- n1)
1303     succ = (n1 -- n2)
1304     swap = (a1 a2 -- a2 a1)
1305     swons = ([.0.] a0 -- [a0 .0.])
1306     third = ([a0 a1 a2 .0.] -- a2)
1307     tuck = (a2 a1 -- a1 a2 a1)
1308     uncons = ([a1 .1.] -- a1 [.1.])
1309
1310
1311
1312 ```python
1313 globals().update(DEFS)
1314 ```
1315
1316 #### Compose `dup` and `mul`
1317
1318
1319 ```python
1320 C(dup, mul)
1321 ```
1322
1323
1324
1325
1326     ((n0,), (n1,))
1327
1328
1329
1330 Revisit the `F` function, works fine.
1331
1332
1333 ```python
1334 F = reduce(C, (pop, swap, rolldown, rest, rest, cons, cons))
1335 F
1336 ```
1337
1338
1339
1340
1341     (((a0, (a1, s0)), a2, a3, a4), ((a3, (a2, s0)),))
1342
1343
1344
1345
1346 ```python
1347 print doc_from_stack_effect(*F)
1348 ```
1349
1350     ([a0 a1 .0.] a2 a3 a4 -- [a3 a2 .0.])
1351
1352
1353 Some otherwise inefficient functions are no longer to be feared.  We can also get the effect of combinators in some limited cases.
1354
1355
1356 ```python
1357 def neato(*funcs):
1358     print doc_from_stack_effect(*reduce(C, funcs))
1359 ```
1360
1361
1362 ```python
1363 # e.g. [swap] dip
1364 neato(rollup, swap, rolldown)
1365 ```
1366
1367     (a0 a1 a2 -- a1 a0 a2)
1368
1369
1370
1371 ```python
1372 # e.g. [popop] dipd
1373 neato(popdd, rolldown, pop)
1374 ```
1375
1376     (a0 a1 a2 a3 -- a2 a3)
1377
1378
1379
1380 ```python
1381 # Reverse the order of the top three items.
1382 neato(rollup, swap)
1383 ```
1384
1385     (a0 a1 a2 -- a2 a1 a0)
1386
1387
1388 #### `compile_()` version 2
1389 Because the type labels represent themselves as valid Python identifiers the `compile_()` function doesn't need to generate them anymore:
1390
1391
1392 ```python
1393 def compile_(name, f, doc=None):
1394     inputs, outputs = f
1395     if doc is None:
1396         doc = doc_from_stack_effect(inputs, outputs)
1397     i = o = Symbol('stack')
1398     for term in inputs:
1399         i = term, i
1400     for term in outputs:
1401         o = term, o
1402     return '''def %s(stack):
1403     """%s"""
1404     %s = stack
1405     return %s''' % (name, doc, i, o)
1406 ```
1407
1408
1409 ```python
1410 print compile_('F', F)
1411 ```
1412
1413     def F(stack):
1414         """([a0 a1 .0.] a2 a3 a4 -- [a3 a2 .0.])"""
1415         (a4, (a3, (a2, ((a0, (a1, s0)), stack)))) = stack
1416         return ((a3, (a2, s0)), stack)
1417
1418
1419 But it cannot magically create new functions that involve e.g. math and such.  Note that this is *not* a `sqr` function implementation:
1420
1421
1422 ```python
1423 print compile_('sqr', C(dup, mul))
1424 ```
1425
1426     def sqr(stack):
1427         """(n0 -- n1)"""
1428         (n0, stack) = stack
1429         return (n1, stack)
1430
1431
1432 (Eventually I should come back around to this becuase it's not tooo difficult to exend this code to be able to compile e.g. `n3 = mul(n1, n2)` for `mul` and insert it in the right place with the right variable names.  It requires a little more support from the library functions, in that we need to know to call `mul()` the Python function for `mul` the Joy function, but since *most* of the math functions (at least) are already wrappers it should be straightforward.)
1433
1434 #### `compilable()`
1435 The functions that *can* be compiled are the ones that have only `AnyJoyType` and `StackJoyType` labels in their stack effect comments.  We can write a function to check that:
1436
1437
1438 ```python
1439 from itertools import imap
1440
1441
1442 def compilable(f):
1443     return isinstance(f, tuple) and all(imap(compilable, f)) or stacky(f)
1444 ```
1445
1446
1447 ```python
1448 for name, stack_effect_comment in sorted(defs().items()):
1449     if compilable(stack_effect_comment):
1450         print name, '=', doc_from_stack_effect(*stack_effect_comment)
1451 ```
1452
1453     ccons = (a0 a1 [.0.] -- [a0 a1 .0.])
1454     cons = (a1 [.1.] -- [a1 .1.])
1455     dup = (a1 -- a1 a1)
1456     dupd = (a2 a1 -- a2 a2 a1)
1457     first = ([a1 .1.] -- a1)
1458     over = (a2 a1 -- a2 a1 a2)
1459     pop = (a1 --)
1460     popd = (a2 a1 -- a1)
1461     popdd = (a3 a2 a1 -- a2 a1)
1462     popop = (a2 a1 --)
1463     rest = ([a1 .1.] -- [.1.])
1464     rolldown = (a1 a2 a3 -- a2 a3 a1)
1465     rollup = (a1 a2 a3 -- a3 a1 a2)
1466     rrest = ([a0 a1 .0.] -- [.0.])
1467     second = ([a0 a1 .0.] -- a1)
1468     swap = (a1 a2 -- a2 a1)
1469     swons = ([.0.] a0 -- [a0 .0.])
1470     third = ([a0 a1 a2 .0.] -- a2)
1471     tuck = (a2 a1 -- a1 a2 a1)
1472     uncons = ([a1 .1.] -- a1 [.1.])
1473
1474
1475 ## Part V: Functions that use the Stack
1476
1477 Consider the `stack` function which grabs the whole stack, quotes it, and puts it on itself:
1478
1479     stack (...     -- ... [...]        )
1480     stack (... a   -- ... a [a ...]    )
1481     stack (... b a -- ... b a [a b ...])
1482
1483 We would like to represent this in Python somehow. 
1484 To do this we use a simple, elegant trick.
1485
1486     stack         S   -- (         S,           S)
1487     stack     (a, S)  -- (     (a, S),      (a, S))
1488     stack (a, (b, S)) -- ( (a, (b, S)), (a, (b, S)))
1489
1490 Instead of representing the stack effect comments as a single tuple (with N items in it) we use the same cons-list structure to hold the sequence and `unify()` the whole comments.
1491
1492 ### `stack∘uncons`
1493 Let's try composing `stack` and `uncons`.  We want this result:
1494
1495     stack∘uncons (... a -- ... a a [...])
1496
1497 The stack effects are:
1498
1499     stack = S -- (S, S)
1500
1501     uncons = ((a, Z), S) -- (Z, (a, S))
1502
1503 Unifying:
1504
1505       S    -- (S, S) ∘ ((a, Z), S) -- (Z, (a,   S   ))
1506                                                         w/ { S: (a, Z) }
1507     (a, Z) --        ∘             -- (Z, (a, (a, Z)))
1508
1509 So:
1510
1511     stack∘uncons == (a, Z) -- (Z, (a, (a, Z)))
1512
1513 It works.
1514
1515 ### `stack∘uncons∘uncons`
1516 Let's try `stack∘uncons∘uncons`:
1517
1518     (a, S     ) -- (S,      (a, (a, S     ))) ∘ ((b, Z),  S`             ) -- (Z, (b,   S`   ))
1519     
1520                                                                                     w/ { S: (b, Z) }
1521                                                                                     
1522     (a, (b, Z)) -- ((b, Z), (a, (a, (b, Z)))) ∘ ((b, Z),  S`             ) -- (Z, (b,   S`   ))
1523     
1524                                                                                     w/ { S`: (a, (a, (b, Z))) }
1525                                                                                     
1526     (a, (b, Z)) -- ((b, Z), (a, (a, (b, Z)))) ∘ ((b, Z), (a, (a, (b, Z)))) -- (Z, (b, (a, (a, (b, Z)))))
1527
1528     (a, (b, Z)) -- (Z, (b, (a, (a, (b, Z)))))
1529
1530 It works.
1531
1532 #### `compose()` version 2
1533 This function has to be modified to use the new datastructures and it is no longer recursive, instead recursion happens as part of unification.  Further, the first and second of Pöial's rules are now handled automatically by the unification algorithm.
1534
1535
1536 ```python
1537 def compose(f, g):
1538     (f_in, f_out), (g_in, g_out) = f, g
1539     s = unify(g_in, f_out)
1540     if s == False:  # s can also be the empty dict, which is ok.
1541         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
1542     return update(s, (f_in, g_out))
1543 ```
1544
1545 I don't want to rewrite all the defs myself, so I'll write a little conversion function instead.  This is programmer's laziness.
1546
1547
1548 ```python
1549 def sequence_to_stack(seq, stack=StackJoyType(23)):
1550     for item in seq: stack = item, stack
1551     return stack
1552
1553 NEW_DEFS = {
1554     name: (sequence_to_stack(i), sequence_to_stack(o))
1555     for name, (i, o) in DEFS.iteritems()
1556 }
1557 NEW_DEFS['stack'] = S[0], (S[0], S[0])
1558 NEW_DEFS['swaack'] = (S[1], S[0]), (S[0], S[1])
1559 globals().update(NEW_DEFS)
1560 ```
1561
1562
1563 ```python
1564 C(stack, uncons)
1565 ```
1566
1567
1568
1569
1570     ((a0, s0), (s0, (a0, (a0, s0))))
1571
1572
1573
1574
1575 ```python
1576 C(C(stack, uncons), uncons)
1577 ```
1578
1579
1580
1581
1582     ((a0, (a1, s0)), (s0, (a1, (a0, (a0, (a1, s0))))))
1583
1584
1585
1586 The display function should be changed too.
1587
1588 ### `doc_from_stack_effect()` version 2
1589 Clunky junk, but it will suffice for now.
1590
1591
1592 ```python
1593 def doc_from_stack_effect(inputs, outputs):
1594     switch = [False]  # Do we need to display the '...' for the rest of the main stack?
1595     i, o = _f(inputs, switch), _f(outputs, switch)
1596     if switch[0]:
1597         i.append('...')
1598         o.append('...')
1599     return '(%s--%s)' % (
1600         ' '.join(reversed([''] + i)),
1601         ' '.join(reversed(o + [''])),
1602     )
1603
1604
1605 def _f(term, switch):
1606     a = []
1607     while term and isinstance(term, tuple):
1608         item, term = term
1609         a.append(item)
1610     assert isinstance(term, StackJoyType), repr(term)
1611     a = [_to_str(i, term, switch) for i in a]
1612     return a
1613
1614
1615 def _to_str(term, stack, switch):
1616     if not isinstance(term, tuple):
1617         if term == stack:
1618             switch[0] = True
1619             return '[...]'
1620         return (
1621             '[.%i.]' % term.number
1622             if isinstance(term, StackJoyType)
1623             else str(term)
1624         )
1625
1626     a = []
1627     while term and isinstance(term, tuple):
1628         item, term = term
1629         a.append(_to_str(item, stack, switch))
1630     assert isinstance(term, StackJoyType), repr(term)
1631     if term == stack:
1632         switch[0] = True
1633         end = '...'
1634     else:
1635         end = '.%i.' % term.number
1636     a.append(end)
1637     return '[%s]' % ' '.join(a)
1638 ```
1639
1640
1641 ```python
1642 for name, stack_effect_comment in sorted(NEW_DEFS.items()):
1643     print name, '=', doc_from_stack_effect(*stack_effect_comment)
1644 ```
1645
1646     ccons = (a0 a1 [.0.] -- [a0 a1 .0.])
1647     cons = (a1 [.1.] -- [a1 .1.])
1648     divmod_ = (n2 n1 -- n4 n3)
1649     dup = (a1 -- a1 a1)
1650     dupd = (a2 a1 -- a2 a2 a1)
1651     first = ([a1 .1.] -- a1)
1652     mul = (n1 n2 -- n3)
1653     over = (a2 a1 -- a2 a1 a2)
1654     pm = (n2 n1 -- n4 n3)
1655     pop = (a1 --)
1656     popd = (a2 a1 -- a1)
1657     popdd = (a3 a2 a1 -- a2 a1)
1658     popop = (a2 a1 --)
1659     pred = (n1 -- n2)
1660     rest = ([a1 .1.] -- [.1.])
1661     rolldown = (a1 a2 a3 -- a2 a3 a1)
1662     rollup = (a1 a2 a3 -- a3 a1 a2)
1663     rrest = ([a0 a1 .0.] -- [.0.])
1664     second = ([a0 a1 .0.] -- a1)
1665     sqrt = (n0 -- n1)
1666     stack = (... -- ... [...])
1667     succ = (n1 -- n2)
1668     swaack = ([.1.] -- [.0.])
1669     swap = (a1 a2 -- a2 a1)
1670     swons = ([.0.] a0 -- [a0 .0.])
1671     third = ([a0 a1 a2 .0.] -- a2)
1672     tuck = (a2 a1 -- a1 a2 a1)
1673     uncons = ([a1 .1.] -- a1 [.1.])
1674
1675
1676
1677 ```python
1678 print ; print doc_from_stack_effect(*stack)
1679 print ; print doc_from_stack_effect(*C(stack, uncons))
1680 print ; print doc_from_stack_effect(*C(C(stack, uncons), uncons))
1681 print ; print doc_from_stack_effect(*C(C(stack, uncons), cons))
1682 ```
1683
1684     
1685     (... -- ... [...])
1686     
1687     (... a0 -- ... a0 a0 [...])
1688     
1689     (... a1 a0 -- ... a1 a0 a0 a1 [...])
1690     
1691     (... a0 -- ... a0 [a0 ...])
1692
1693
1694
1695 ```python
1696 print doc_from_stack_effect(*C(ccons, stack))
1697 ```
1698
1699     (... a1 a0 [.0.] -- ... [a1 a0 .0.] [[a1 a0 .0.] ...])
1700
1701
1702
1703 ```python
1704 Q = C(ccons, stack)
1705
1706 Q
1707 ```
1708
1709
1710
1711
1712     ((s0, (a0, (a1, s1))), (((a1, (a0, s0)), s1), ((a1, (a0, s0)), s1)))
1713
1714
1715
1716 #### `compile_()` version 3
1717 This makes the `compile_()` function pretty simple as the stack effect comments are now already in the form needed for the Python code:
1718
1719
1720 ```python
1721 def compile_(name, f, doc=None):
1722     i, o = f
1723     if doc is None:
1724         doc = doc_from_stack_effect(i, o)
1725     return '''def %s(stack):
1726     """%s"""
1727     %s = stack
1728     return %s''' % (name, doc, i, o)
1729 ```
1730
1731
1732 ```python
1733 print compile_('Q', Q)
1734 ```
1735
1736     def Q(stack):
1737         """(... a1 a0 [.0.] -- ... [a1 a0 .0.] [[a1 a0 .0.] ...])"""
1738         (s0, (a0, (a1, s1))) = stack
1739         return (((a1, (a0, s0)), s1), ((a1, (a0, s0)), s1))
1740
1741
1742
1743 ```python
1744 unstack = (S[1], S[0]), S[1]
1745 enstacken = S[0], (S[0], S[1])
1746 ```
1747
1748
1749 ```python
1750 print doc_from_stack_effect(*unstack)
1751 ```
1752
1753     ([.1.] --)
1754
1755
1756
1757 ```python
1758 print doc_from_stack_effect(*enstacken)
1759 ```
1760
1761     (-- [.0.])
1762
1763
1764
1765 ```python
1766 print doc_from_stack_effect(*C(cons, unstack))
1767 ```
1768
1769     (a0 [.0.] -- a0)
1770
1771
1772
1773 ```python
1774 print doc_from_stack_effect(*C(cons, enstacken))
1775 ```
1776
1777     (a0 [.0.] -- [[a0 .0.] .1.])
1778
1779
1780
1781 ```python
1782 C(cons, unstack)
1783 ```
1784
1785
1786
1787
1788     ((s0, (a0, s1)), (a0, s0))
1789
1790
1791
1792 ## Part VI: Multiple Stack Effects
1793 ...
1794
1795
1796 ```python
1797 class IntJoyType(NumberJoyType): prefix = 'i'
1798
1799
1800 F = map(FloatJoyType, _R)
1801 I = map(IntJoyType, _R)
1802 ```
1803
1804
1805 ```python
1806 muls = [
1807      ((I[2], (I[1], S[0])), (I[3], S[0])),
1808      ((F[2], (I[1], S[0])), (F[3], S[0])),
1809      ((I[2], (F[1], S[0])), (F[3], S[0])),
1810      ((F[2], (F[1], S[0])), (F[3], S[0])),
1811 ]
1812 ```
1813
1814
1815 ```python
1816 for f in muls:
1817     print doc_from_stack_effect(*f)
1818 ```
1819
1820     (i1 i2 -- i3)
1821     (i1 f2 -- f3)
1822     (f1 i2 -- f3)
1823     (f1 f2 -- f3)
1824
1825
1826
1827 ```python
1828 for f in muls:
1829     try:
1830         e = C(dup, f)
1831     except TypeError:
1832         continue
1833     print doc_from_stack_effect(*dup), doc_from_stack_effect(*f), doc_from_stack_effect(*e)
1834 ```
1835
1836     (a1 -- a1 a1) (i1 i2 -- i3) (i0 -- i1)
1837     (a1 -- a1 a1) (f1 f2 -- f3) (f0 -- f1)
1838
1839
1840
1841 ```python
1842 from itertools import product
1843
1844
1845 def meta_compose(F, G):
1846     for f, g in product(F, G):
1847         try:
1848             yield C(f, g)
1849         except TypeError:
1850             pass
1851
1852
1853 def MC(F, G):
1854     return sorted(set(meta_compose(F, G)))
1855 ```
1856
1857
1858 ```python
1859 for f in MC([dup], muls):
1860     print doc_from_stack_effect(*f)
1861 ```
1862
1863     (f0 -- f1)
1864     (i0 -- i1)
1865
1866
1867
1868 ```python
1869 for f in MC([dup], [mul]):
1870     print doc_from_stack_effect(*f)
1871 ```
1872
1873     (n0 -- n1)
1874
1875
1876 ### Representing an Unbounded Sequence of Types
1877
1878 We can borrow a trick from [Brzozowski's Derivatives of Regular Expressions](https://en.wikipedia.org/wiki/Brzozowski_derivative) to invent a new type of type variable, a "sequence type" (I think this is what they mean in the literature by that term...) or "[Kleene Star](https://en.wikipedia.org/wiki/Kleene_star)" type.  I'm going to represent it as a type letter and the asterix, so a sequence of zero or more `AnyJoyType` variables would be:
1879
1880     A*
1881
1882 The `A*` works by splitting the universe into two alternate histories:
1883
1884     A* -> 0 | A A*
1885
1886 The Kleene star variable disappears in one universe, and in the other it turns into an `AnyJoyType` variable followed by itself again.  We have to return all universes (represented by their substitution dicts, the "unifiers") that don't lead to type conflicts.
1887
1888 Consider unifying two stacks (the lowercase letters are any type variables of the kinds we have defined so far):
1889
1890     [a A* b .0.] U [c d .1.]
1891                               w/ {c: a}
1892     [  A* b .0.] U [  d .1.]
1893
1894 Now we have to split universes to unify `A*`.  In the first universe it disappears:
1895
1896     [b .0.] U [d .1.]
1897                        w/ {d: b, .1.: .0.} 
1898          [] U []
1899
1900 While in the second it spawns an `A`, which we will label `e`:
1901
1902     [e A* b .0.] U [d .1.]
1903                             w/ {d: e}
1904     [  A* b .0.] U [  .1.]
1905                             w/ {.1.: A* b .0.}
1906     [  A* b .0.] U [  A* b .0.]
1907
1908 Giving us two unifiers:
1909
1910     {c: a,  d: b,  .1.:      .0.}
1911     {c: a,  d: e,  .1.: A* b .0.}
1912
1913
1914 ```python
1915 class KleeneStar(object):
1916
1917     kind = AnyJoyType
1918
1919     def __init__(self, number):
1920         self.number = number
1921         self.count = 0
1922         self.prefix = repr(self)
1923
1924     def __repr__(self):
1925         return '%s%i*' % (self.kind.prefix, self.number)
1926
1927     def another(self):
1928         self.count += 1
1929         return self.kind(10000 * self.number + self.count)
1930
1931     def __eq__(self, other):
1932         return (
1933             isinstance(other, self.__class__)
1934             and other.number == self.number
1935         )
1936
1937     def __ge__(self, other):
1938         return self.kind >= other.kind
1939
1940     def __add__(self, other):
1941         return self.__class__(self.number + other)
1942     __radd__ = __add__
1943     
1944     def __hash__(self):
1945         return hash(repr(self))
1946
1947 class AnyStarJoyType(KleeneStar): kind = AnyJoyType
1948 class NumberStarJoyType(KleeneStar): kind = NumberJoyType
1949 #class FloatStarJoyType(KleeneStar): kind = FloatJoyType
1950 #class IntStarJoyType(KleeneStar): kind = IntJoyType
1951 class StackStarJoyType(KleeneStar): kind = StackJoyType
1952
1953
1954 As = map(AnyStarJoyType, _R)
1955 Ns = map(NumberStarJoyType, _R)
1956 Ss = map(StackStarJoyType, _R)
1957 ```
1958
1959 #### `unify()` version 4
1960 Can now return multiple results...
1961
1962
1963 ```python
1964 def unify(u, v, s=None):
1965     if s is None:
1966         s = {}
1967     elif s:
1968         u = update(s, u)
1969         v = update(s, v)
1970
1971     if u == v:
1972         return s,
1973
1974     if isinstance(u, AnyJoyType) and isinstance(v, AnyJoyType):
1975         if u >= v:
1976             s[u] = v
1977             return s,
1978         if v >= u:
1979             s[v] = u
1980             return s,
1981         raise TypeError('Cannot unify %r and %r.' % (u, v))
1982
1983     if isinstance(u, tuple) and isinstance(v, tuple):
1984         if len(u) != len(v) != 2:
1985             raise TypeError(repr((u, v)))
1986             
1987         a, b = v
1988         if isinstance(a, KleeneStar):
1989             # Two universes, in one the Kleene star disappears and unification
1990             # continues without it...
1991             s0 = unify(u, b)
1992             
1993             # In the other it spawns a new variable.
1994             s1 = unify(u, (a.another(), v))
1995             
1996             t = s0 + s1
1997             for sn in t:
1998                 sn.update(s)
1999             return t
2000
2001         a, b = u
2002         if isinstance(a, KleeneStar):
2003             s0 = unify(v, b)
2004             s1 = unify(v, (a.another(), u))
2005             t = s0 + s1
2006             for sn in t:
2007                 sn.update(s)
2008             return t
2009
2010         ses = unify(u[0], v[0], s)
2011         results = ()
2012         for sn in ses:
2013             results += unify(u[1], v[1], sn)
2014         return results
2015  
2016     if isinstance(v, tuple):
2017         if not stacky(u):
2018             raise TypeError('Cannot unify %r and %r.' % (u, v))
2019         s[u] = v
2020         return s,
2021
2022     if isinstance(u, tuple):
2023         if not stacky(v):
2024             raise TypeError('Cannot unify %r and %r.' % (v, u))
2025         s[v] = u
2026         return s,
2027
2028     return ()
2029
2030
2031 def stacky(thing):
2032     return thing.__class__ in {AnyJoyType, StackJoyType}
2033 ```
2034
2035
2036 ```python
2037 a = (As[1], S[1])
2038 a
2039 ```
2040
2041
2042
2043
2044     (a1*, s1)
2045
2046
2047
2048
2049 ```python
2050 b = (A[1], S[2])
2051 b
2052 ```
2053
2054
2055
2056
2057     (a1, s2)
2058
2059
2060
2061
2062 ```python
2063 for result in unify(b, a):
2064     print result, '->', update(result, a), update(result, b)
2065 ```
2066
2067     {s1: (a1, s2)} -> (a1*, (a1, s2)) (a1, s2)
2068     {a1: a10001, s2: (a1*, s1)} -> (a1*, s1) (a10001, (a1*, s1))
2069
2070
2071
2072 ```python
2073 for result in unify(a, b):
2074     print result, '->', update(result, a), update(result, b)
2075 ```
2076
2077     {s1: (a1, s2)} -> (a1*, (a1, s2)) (a1, s2)
2078     {a1: a10002, s2: (a1*, s1)} -> (a1*, s1) (a10002, (a1*, s1))
2079
2080
2081
2082     (a1*, s1)       [a1*]       (a1, s2)        [a1]
2083
2084     (a1*, (a1, s2)) [a1* a1]    (a1, s2)        [a1]
2085
2086     (a1*, s1)       [a1*]       (a2, (a1*, s1)) [a2 a1*]
2087
2088
2089 ```python
2090 sum_ = ((Ns[1], S[1]), S[0]), (N[0], S[0])
2091
2092 print doc_from_stack_effect(*sum_)
2093 ```
2094
2095     ([n1* .1.] -- n0)
2096
2097
2098
2099 ```python
2100 f = (N[1], (N[2], (N[3], S[1]))), S[0]
2101
2102 print doc_from_stack_effect(S[0], f)
2103 ```
2104
2105     (-- [n1 n2 n3 .1.])
2106
2107
2108
2109 ```python
2110 for result in unify(sum_[0], f):
2111     print result, '->', update(result, sum_[1])
2112 ```
2113
2114     {s1: (n1, (n2, (n3, s1)))} -> (n0, s0)
2115     {n1: n10001, s1: (n2, (n3, s1))} -> (n0, s0)
2116     {n1: n10001, s1: (n3, s1), n2: n10002} -> (n0, s0)
2117     {n1: n10001, s1: (n1*, s1), n3: n10003, n2: n10002} -> (n0, s0)
2118
2119
2120 #### `compose()` version 3
2121 This function has to be modified to yield multiple results.
2122
2123
2124 ```python
2125 def compose(f, g):
2126     (f_in, f_out), (g_in, g_out) = f, g
2127     s = unify(g_in, f_out)
2128     if not s:
2129         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
2130     for result in s:
2131         yield update(result, (f_in, g_out))
2132
2133 ```
2134
2135
2136 ```python
2137 def meta_compose(F, G):
2138     for f, g in product(F, G):
2139         try:
2140             for result in C(f, g):
2141                 yield result
2142         except TypeError:
2143             pass
2144
2145
2146 def C(f, g):
2147     f, g = relabel(f, g)
2148     for fg in compose(f, g):
2149         yield delabel(fg)
2150 ```
2151
2152
2153 ```python
2154 for f in MC([dup], muls):
2155     print doc_from_stack_effect(*f)
2156 ```
2157
2158     (f0 -- f1)
2159     (i0 -- i1)
2160
2161
2162
2163 ```python
2164
2165
2166 for f in MC([dup], [sum_]):
2167     print doc_from_stack_effect(*f)
2168 ```
2169
2170     ([n0* .0.] -- [n0* .0.] n0)
2171
2172
2173
2174 ```python
2175
2176
2177 for f in MC([cons], [sum_]):
2178     print doc_from_stack_effect(*f)
2179 ```
2180
2181     (a0 [.0.] -- n0)
2182     (n0 [n0* .0.] -- n1)
2183
2184
2185
2186 ```python
2187 sum_ = (((N[1], (Ns[1], S[1])), S[0]), (N[0], S[0]))
2188 print doc_from_stack_effect(*cons),
2189 print doc_from_stack_effect(*sum_),
2190
2191 for f in MC([cons], [sum_]):
2192     print doc_from_stack_effect(*f)
2193 ```
2194
2195     (a1 [.1.] -- [a1 .1.]) ([n1 n1* .1.] -- n0) (n0 [n0* .0.] -- n1)
2196
2197
2198
2199 ```python
2200 a = (A[4], (As[1], (A[3], S[1])))
2201 a
2202 ```
2203
2204
2205
2206
2207     (a4, (a1*, (a3, s1)))
2208
2209
2210
2211
2212 ```python
2213 b = (A[1], (A[2], S[2]))
2214 b
2215 ```
2216
2217
2218
2219
2220     (a1, (a2, s2))
2221
2222
2223
2224
2225 ```python
2226 for result in unify(b, a):
2227     print result
2228 ```
2229
2230     {a1: a4, s2: s1, a2: a3}
2231     {a1: a4, s2: (a1*, (a3, s1)), a2: a10003}
2232
2233
2234
2235 ```python
2236 for result in unify(a, b):
2237     print result
2238 ```
2239
2240     {s2: s1, a2: a3, a4: a1}
2241     {s2: (a1*, (a3, s1)), a2: a10004, a4: a1}
2242
2243
2244 ## Part VII: Typing Combinators
2245
2246 In order to compute the stack effect of combinators you kinda have to have the quoted programs they expect available.  In the most general case, the `i` combinator, you can't say anything about it's stack effect other than it expects one quote:
2247
2248     i (... [.1.] -- ... .1.)
2249
2250 Or
2251
2252     i (... [A* .1.] -- ... A*)
2253
2254 Consider the type of:
2255
2256     [cons] dip
2257
2258 Obviously it would be:
2259
2260     (a1 [..1] a2 -- [a1 ..1] a2)
2261
2262 `dip` itself could have:
2263
2264     (a1 [..1] -- ... then what?
2265
2266
2267 Without any information about the contents of the quote we can't say much about the result.
2268
2269 I think there's a way forward.  If we convert our list of terms we are composing into a stack structure we can use it as a *Joy expression*, then we can treat the *output half* of a function's stack effect comment as a Joy interpreter stack, and just execute combinators directly.  We can hybridize the compostition function with an interpreter to evaluate combinators, compose non-combinator functions, and put type variables on the stack.  For combinators like `branch` that can have more than one stack effect we have to "split universes" again and return both.
2270
2271
2272 ```python
2273 class FunctionJoyType(AnyJoyType):
2274
2275     def __init__(self, name, sec, number):
2276         self.name = name
2277         self.stack_effects = sec
2278         self.number = number
2279
2280     def __add__(self, other):
2281         return self
2282     __radd__ = __add__
2283
2284     def __repr__(self):
2285         return self.name
2286
2287
2288 class SymbolJoyType(FunctionJoyType): prefix = 'F'
2289 class CombinatorJoyType(FunctionJoyType): prefix = 'C'
2290
2291
2292
2293 ```
2294
2295
2296 ```python
2297 def flatten(g):
2298     return list(chain.from_iterable(g))
2299
2300
2301 ID = S[0], S[0]  # Identity function.
2302
2303
2304 def infer(e, F=ID):
2305     if not e:
2306         return [F]
2307
2308     n, e = e
2309
2310     if isinstance(n, SymbolJoyType):
2311         res = flatten(infer(e, Fn) for Fn in MC([F], n.stack_effects))
2312
2313     elif isinstance(n, CombinatorJoyType):
2314         res = []
2315         for combinator in n.stack_effects:
2316             fi, fo = F
2317             new_fo, ee, _ = combinator(fo, e, {})
2318             ee = update(FUNCTIONS, ee)  # Fix Symbols.
2319             new_F = fi, new_fo
2320             res.extend(infer(ee, new_F))
2321     else:
2322         lit = s9, (n, s9)
2323         res = flatten(infer(e, Fn) for Fn in MC([F], [lit]))
2324
2325     return res
2326
2327
2328 ```
2329
2330
2331 ```python
2332 f0, f1, f2, f3, f4, f5, f6, f7, f8, f9 = F = map(FloatJoyType, _R)
2333 i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 = I = map(IntJoyType, _R)
2334 n0, n1, n2, n3, n4, n5, n6, n7, n8, n9 = N
2335 s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 = S
2336 ```
2337
2338
2339 ```python
2340 import joy.library
2341
2342 FNs = '''ccons cons divmod_ dup dupd first
2343          over pm pop popd popdd popop pred
2344          rest rolldown rollup rrest second
2345          sqrt stack succ swaack swap swons
2346          third tuck uncons'''
2347
2348 FUNCTIONS = {
2349     name: SymbolJoyType(name, [NEW_DEFS[name]], i)
2350     for i, name in enumerate(FNs.strip().split())
2351     }
2352 FUNCTIONS['sum'] = SymbolJoyType('sum', [(((Ns[1], s1), s0), (n0, s0))], 100)
2353 FUNCTIONS['mul'] = SymbolJoyType('mul', [
2354      ((i2, (i1, s0)), (i3, s0)),
2355      ((f2, (i1, s0)), (f3, s0)),
2356      ((i2, (f1, s0)), (f3, s0)),
2357      ((f2, (f1, s0)), (f3, s0)),
2358 ], 101)
2359 FUNCTIONS.update({
2360     combo.__name__: CombinatorJoyType(combo.__name__, [combo], i)
2361     for i, combo in enumerate((
2362         joy.library.i,
2363         joy.library.dip,
2364         joy.library.dipd,
2365         joy.library.dipdd,
2366         joy.library.dupdip,
2367         joy.library.b,
2368         joy.library.x,
2369         joy.library.infra,
2370         ))
2371     })
2372
2373 def branch_true(stack, expression, dictionary):
2374     (then, (else_, (flag, stack))) = stack
2375     return stack, CONCAT(then, expression), dictionary
2376
2377 def branch_false(stack, expression, dictionary):
2378     (then, (else_, (flag, stack))) = stack
2379     return stack, CONCAT(else_, expression), dictionary
2380
2381 FUNCTIONS['branch'] = CombinatorJoyType('branch', [branch_true, branch_false], 100)
2382 ```
2383
2384
2385 ```python
2386 globals().update(FUNCTIONS)
2387 ```
2388
2389
2390 ```python
2391 from itertools import chain
2392 from joy.utils.stack import list_to_stack as l2s
2393 ```
2394
2395
2396 ```python
2397 expression = l2s([n1, n2, (mul, s2), (stack, s3), dip, infra, first])
2398 ```
2399
2400
2401 ```python
2402 expression
2403 ```
2404
2405
2406
2407
2408     (n1, (n2, ((mul, s2), ((stack, s3), (dip, (infra, (first, ())))))))
2409
2410
2411
2412
2413 ```python
2414 expression = l2s([n1, n2, mul])
2415 ```
2416
2417
2418 ```python
2419 infer(expression)
2420 ```
2421
2422
2423
2424
2425     []
2426
2427
2428
2429
2430 ```python
2431 class SymbolJoyType(AnyJoyType):
2432     prefix = 'F'
2433
2434     def __init__(self, name, sec, number):
2435         self.name = name
2436         self.stack_effects = sec
2437         self.number = number
2438
2439 class CombinatorJoyType(SymbolJoyType): prefix = 'C'
2440
2441 def dip_t(stack, expression):
2442     (quote, (a1, stack)) = stack
2443     expression = stack_concat(quote, (a1, expression))
2444     return stack, expression
2445
2446 CONS = SymbolJoyType('cons', [cons], 23)
2447 DIP = CombinatorJoyType('dip', [dip_t], 44)
2448
2449
2450 def kav(F, e):
2451     #i, stack = F
2452     if not e:
2453         return [(F, e)]
2454     n, e = e
2455     if isinstance(n, SymbolJoyType):
2456         Fs = []
2457         for sec in n.stack_effects:
2458             Fs.extend(MC([F], sec))
2459         return [kav(Fn, e) for Fn in Fs]
2460     if isinstance(n, CombinatorJoyType):
2461         res = []
2462         for f in n.stack_effects:
2463             s, e = f(F[1], e)
2464             new_F = F[0], s
2465             res.extend(kav(new_F, e))
2466         return res
2467     lit = S[0], (n, S[0])
2468     return [kav(Fn, e) for Fn in MC([F], [lit])]
2469
2470 ```
2471
2472 compare, and be amazed:
2473
2474
2475 ```python
2476 def dip_t(stack, expression):
2477     (quote, (a1, stack)) = stack
2478     expression = stack_concat(quote, (a1, expression))
2479     return stack, expression
2480 ```
2481
2482
2483 ```python
2484 def dip(stack, expression, dictionary):
2485     (quote, (x, stack)) = stack
2486     expression = (x, expression)
2487     return stack, concat(quote, expression), dictionary
2488 ```
2489
2490 And that brings us to current Work-In-Progress.  I'm pretty hopeful that the mixed-mode inferencer/interpreter `kav()` function along with the ability to specify multiple implementations for the combinators will permit modelling of the stack effects of e.g. `ifte`.  If I can keep up the pace I should be able to verify that conjecture by the end of June.
2491
2492 The rest of this stuff is junk and/or unfinished material.
2493
2494 ## Appendix: Joy in the Logical Paradigm
2495 For this to work the type label classes have to be modified to let `T >= t` succeed, where e.g. `T` is `IntJoyType` and `t` is `int`
2496
2497
2498 ```python
2499 F = reduce(C, (pop, swap, rolldown, rest, rest, cons, cons))
2500
2501 print doc_from_stack_effect(*F)
2502 ```
2503
2504
2505     ---------------------------------------------------------------------------
2506
2507     TypeError                                 Traceback (most recent call last)
2508
2509     <ipython-input-119-7fde90b4e88f> in <module>()
2510           1 F = reduce(C, (pop, swap, rolldown, rest, rest, cons, cons))
2511           2 
2512     ----> 3 print doc_from_stack_effect(*F)
2513     
2514
2515     <ipython-input-98-ddee30dbb1a6> in C(f, g)
2516          10 def C(f, g):
2517          11     f, g = relabel(f, g)
2518     ---> 12     for fg in compose(f, g):
2519          13         yield delabel(fg)
2520
2521
2522     <ipython-input-97-5eb7ac5ad2c2> in compose(f, g)
2523           1 def compose(f, g):
2524     ----> 2     (f_in, f_out), (g_in, g_out) = f, g
2525           3     s = unify(g_in, f_out)
2526           4     if not s:
2527           5         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
2528
2529
2530     <ipython-input-98-ddee30dbb1a6> in C(f, g)
2531          10 def C(f, g):
2532          11     f, g = relabel(f, g)
2533     ---> 12     for fg in compose(f, g):
2534          13         yield delabel(fg)
2535
2536
2537     <ipython-input-97-5eb7ac5ad2c2> in compose(f, g)
2538           1 def compose(f, g):
2539     ----> 2     (f_in, f_out), (g_in, g_out) = f, g
2540           3     s = unify(g_in, f_out)
2541           4     if not s:
2542           5         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
2543
2544
2545     <ipython-input-98-ddee30dbb1a6> in C(f, g)
2546          10 def C(f, g):
2547          11     f, g = relabel(f, g)
2548     ---> 12     for fg in compose(f, g):
2549          13         yield delabel(fg)
2550
2551
2552     <ipython-input-97-5eb7ac5ad2c2> in compose(f, g)
2553           1 def compose(f, g):
2554     ----> 2     (f_in, f_out), (g_in, g_out) = f, g
2555           3     s = unify(g_in, f_out)
2556           4     if not s:
2557           5         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
2558
2559
2560     <ipython-input-98-ddee30dbb1a6> in C(f, g)
2561          10 def C(f, g):
2562          11     f, g = relabel(f, g)
2563     ---> 12     for fg in compose(f, g):
2564          13         yield delabel(fg)
2565
2566
2567     <ipython-input-97-5eb7ac5ad2c2> in compose(f, g)
2568           1 def compose(f, g):
2569     ----> 2     (f_in, f_out), (g_in, g_out) = f, g
2570           3     s = unify(g_in, f_out)
2571           4     if not s:
2572           5         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
2573
2574
2575     <ipython-input-98-ddee30dbb1a6> in C(f, g)
2576          10 def C(f, g):
2577          11     f, g = relabel(f, g)
2578     ---> 12     for fg in compose(f, g):
2579          13         yield delabel(fg)
2580
2581
2582     <ipython-input-97-5eb7ac5ad2c2> in compose(f, g)
2583           1 def compose(f, g):
2584     ----> 2     (f_in, f_out), (g_in, g_out) = f, g
2585           3     s = unify(g_in, f_out)
2586           4     if not s:
2587           5         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
2588
2589
2590     <ipython-input-98-ddee30dbb1a6> in C(f, g)
2591          10 def C(f, g):
2592          11     f, g = relabel(f, g)
2593     ---> 12     for fg in compose(f, g):
2594          13         yield delabel(fg)
2595
2596
2597     <ipython-input-97-5eb7ac5ad2c2> in compose(f, g)
2598           1 def compose(f, g):
2599     ----> 2     (f_in, f_out), (g_in, g_out) = f, g
2600           3     s = unify(g_in, f_out)
2601           4     if not s:
2602           5         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
2603
2604
2605     TypeError: 'SymbolJoyType' object is not iterable
2606
2607
2608
2609 ```python
2610 from joy.parser import text_to_expression
2611 ```
2612
2613
2614 ```python
2615 s = text_to_expression('[3 4 ...] 2 1')
2616 s
2617 ```
2618
2619
2620 ```python
2621 L = unify(F[1], s)
2622 L
2623 ```
2624
2625
2626 ```python
2627 F[1]
2628 ```
2629
2630
2631 ```python
2632 F[1][0]
2633 ```
2634
2635
2636 ```python
2637 s[0]
2638 ```
2639
2640 ## [Abstract Interpretation](https://en.wikipedia.org/wiki/Abstract_interpretation)
2641 I *think* this might be sorta what I'm doing above with the `kav()` function...
2642   In any event "mixed-mode" interpreters that include values and type variables and can track constraints, etc. will be, uh, super-useful.  And Abstract Interpretation should be a rich source of ideas.
2643
2644
2645 ## Junk
2646
2647
2648 ```python
2649 class SymbolJoyType(AnyJoyType): prefix = 'F'
2650
2651 W = map(SymbolJoyType, _R)
2652
2653 k = S[0], ((W[1], S[2]), S[0])
2654 Symbol('cons')
2655 print doc_from_stack_effect(*k)
2656
2657 ```
2658
2659
2660 ```python
2661 dip_a = ((W[1], S[2]), (A[1], S[0]))
2662 ```
2663
2664
2665 ```python
2666 d = relabel(S[0], dip_a)
2667 print doc_from_stack_effect(*d)
2668 ```
2669
2670
2671 ```python
2672 s = list(unify(d[1], k[1]))[0]
2673 s
2674 ```
2675
2676
2677 ```python
2678 j = update(s, k)
2679 ```
2680
2681
2682 ```python
2683 print doc_from_stack_effect(*j)
2684 ```
2685
2686
2687 ```python
2688 j
2689 ```
2690
2691
2692 ```python
2693 cons
2694 ```
2695
2696
2697 ```python
2698 for f in MC([k], [dup]):
2699     print doc_from_stack_effect(*f)
2700 ```
2701
2702
2703 ```python
2704 l = S[0], ((cons, S[2]), (A[1], S[0]))
2705 ```
2706
2707
2708 ```python
2709 print doc_from_stack_effect(*l)
2710 ```
2711
2712
2713 ```python
2714
2715 def dip_t(F):
2716     (quote, (a1, sec)) = F[1]
2717     G = F[0], sec
2718     P = S[3], (a1, S[3])
2719     a = [P]
2720     while isinstance(quote, tuple):
2721         term, quote = quote
2722         a.append(term)
2723     a.append(G)
2724     return a[::-1]
2725
2726
2727
2728
2729 ```
2730
2731
2732 ```python
2733 from joy.utils.stack import iter_stack
2734 ```
2735
2736
2737 ```python
2738 a, b, c = dip_t(l)
2739 ```
2740
2741
2742 ```python
2743 a
2744 ```
2745
2746
2747 ```python
2748 b
2749 ```
2750
2751
2752 ```python
2753 c
2754 ```
2755
2756
2757 ```python
2758 MC([a], [b])
2759 ```
2760
2761
2762 ```python
2763 kjs = MC(MC([a], [b]), [c])
2764 kjs
2765 ```
2766
2767
2768 ```python
2769 print doc_from_stack_effect(*kjs[0])
2770 ```
2771
2772     (a0 [.0.] -- [a0 .0.] a1)
2773     
2774        a0 [.0.] a1 [cons] dip
2775     ----------------------------
2776        [a0 .0.] a1
2777
2778 ### `concat`
2779
2780 How to deal with `concat`?
2781
2782     concat ([.0.] [.1.] -- [.0. .1.])
2783     
2784 We would like to represent this in Python somehow...
2785
2786
2787 ```python
2788 concat = (S[0], S[1]), ((S[0], S[1]),)
2789 ```
2790
2791 But this is actually `cons` with the first argument restricted to be a stack:
2792
2793     ([.0.] [.1.] -- [[.0.] .1.])
2794
2795 What we have implemented so far would actually only permit:
2796
2797     ([.0.] [.1.] -- [.2.])
2798
2799
2800 ```python
2801 concat = (S[0], S[1]), (S[2],)
2802 ```
2803
2804     
2805 Which works but can lose information.  Consider `cons concat`, this is how much information we *could* retain:
2806
2807     (1 [.0.] [.1.] -- [1 .0. .1.])
2808
2809 As opposed to just:
2810
2811     (1 [.0.] [.1.] -- [.2.])
2812
2813 ### represent `concat`
2814
2815     ([.0.] [.1.] -- [A*(.0.) .1.])
2816
2817 Meaning that `A*` on the right-hand side should all the crap from `.0.`.
2818
2819     ([      .0.] [.1.] -- [      A* .1.])
2820     ([a     .0.] [.1.] -- [a     A* .1.])
2821     ([a b   .0.] [.1.] -- [a b   A* .1.])
2822     ([a b c .0.] [.1.] -- [a b c A* .1.])
2823
2824     
2825
2826 or...
2827
2828     ([       .0.] [.1.] -- [       .1.])
2829     ([a      .0.] [.1.] -- [a      .1.])
2830     ([a b    .0.] [.1.] -- [a b    .1.])
2831     ([a b  c .0.] [.1.] -- [a b  c .1.])
2832     ([a A* c .0.] [.1.] -- [a A* c .1.])
2833
2834     
2835
2836     (a, (b, S0)) . S1 = (a, (b, (A*, S1)))
2837
2838
2839 ```python
2840 class Astar(object):
2841     def __repr__(self):
2842         return 'A*'
2843
2844
2845 def concat(s0, s1):
2846     a = []
2847     while isinstance(s0, tuple):
2848         term, s0 = s0
2849         a.append(term)
2850     assert isinstance(s0, StackJoyType), repr(s0)
2851     s1 = Astar(), s1
2852     for term in reversed(a):
2853         s1 = term, s1
2854     return s1
2855 ```
2856
2857
2858 ```python
2859 a, b = (A[1], S[0]), (A[2], S[1])
2860 ```
2861
2862
2863 ```python
2864 concat(a, b)
2865 ```