OSDN Git Service

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