OSDN Git Service

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