OSDN Git Service

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