+++ /dev/null
-The Blissful Elegance of Typing Joy
-===================================
-
-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.)
-
-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.)
-
-Part I: Pöial’s Rules
----------------------
-
-`“Typing Tools for Typeless Stack Languages” by Jaanus
-Pöial <http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.212.6026>`__
-
-::
-
- @INPROCEEDINGS{Pöial06typingtools,
- author = {Jaanus Pöial},
- title = {Typing tools for typeless stack languages},
- booktitle = {In 23rd Euro-Forth Conference},
- year = {2006},
- pages = {40--46}
- }
-
-First Rule
-~~~~~~~~~~
-
-This rule deals with functions (and literals) that put items on the
-stack ``(-- d)``:
-
-::
-
- (a -- b)∘(-- d)
- ---------------------
- (a -- b d)
-
-Second Rule
-~~~~~~~~~~~
-
-This rule deals with functions that consume items from the stack
-``(a --)``:
-
-::
-
- (a --)∘(c -- d)
- ---------------------
- (c a -- d)
-
-Third Rule
-~~~~~~~~~~
-
-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.
-
-::
-
- (a -- b t[i])∘(c u[j] -- d) t <= u (t is subtype of u)
- -------------------------------
- (a -- b )∘(c -- d) t[i] == t[k] == u[j]
- ^
-
- (a -- b t[i])∘(c u[j] -- d) u <= t (u is subtype of t)
- -------------------------------
- (a -- b )∘(c -- d) t[i] == u[k] == u[j]
-
-Let’s work through some examples by hand to develop an intuition for the
-algorithm.
-
-There’s a function in one of the other notebooks.
-
-::
-
- F == pop swap roll< rest rest cons cons
-
-It’s all “stack chatter” and list manipulation so we should be able to
-deduce its type.
-
-Stack Effect Comments
-~~~~~~~~~~~~~~~~~~~~~
-
-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):
-
-::
-
- pop (1 --)
-
- swap (1 2 -- 2 1)
-
- roll< (1 2 3 -- 2 3 1)
-
-These commands alter the stack but don’t “look at” the values so these
-numbers represent an “Any type”.
-
-``pop swap``
-~~~~~~~~~~~~
-
-::
-
- (1 --) (1 2 -- 2 1)
-
-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:
-
-::
-
- (0 --) (1 2 -- 2 1)
-
-Following the second rule:
-
-::
-
- (1 2 0 -- 2 1)
-
-``pop∘swap roll<``
-~~~~~~~~~~~~~~~~~~
-
-::
-
- (1 2 0 -- 2 1) (1 2 3 -- 2 3 1)
-
-Let’s re-label them:
-
-::
-
- (1a 2a 0a -- 2a 1a) (1b 2b 3b -- 2b 3b 1b)
-
-Now we follow the rules.
-
-We must unify ``1a`` and ``3b``, and ``2a`` and ``2b``, replacing the
-terms in the forms:
-
-::
-
- (1a 2a 0a -- 2a 1a) (1b 2b 3b -- 2b 3b 1b)
- w/ {1a: 3b}
- (3b 2a 0a -- 2a ) (1b 2b -- 2b 3b 1b)
- w/ {2a: 2b}
- (3b 2b 0a -- ) (1b -- 2b 3b 1b)
-
-Here we must apply the second rule:
-
-::
-
- (3b 2b 0a --) (1b -- 2b 3b 1b)
- -----------------------------------
- (1b 3b 2b 0a -- 2b 3b 1b)
-
-Now we de-label the type, uh, labels:
-
-::
-
- (1b 3b 2b 0a -- 2b 3b 1b)
-
- w/ {
- 1b: 1,
- 3b: 2,
- 2b: 3,
- 0a: 0,
- }
-
- (1 2 3 0 -- 3 2 1)
-
-And now we have the stack effect comment for ``pop∘swap∘roll<``.
-
-Compiling ``pop∘swap∘roll<``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The simplest way to “compile” this function would be something like:
-
-.. code:: ipython2
-
- def poswrd(s, e, d):
- return rolldown(*swap(*pop(s, e, d)))
-
-However, internally this function would still be allocating tuples
-(stack cells) and doing other unnecesssary work.
-
-Looking ahead for a moment, from the stack effect comment:
-
-::
-
- (1 2 3 0 -- 3 2 1)
-
-We should be able to directly write out a Python function like:
-
-.. code:: ipython2
-
- def poswrd(stack):
- (_, (a, (b, (c, stack)))) = stack
- return (c, (b, (a, stack)))
-
-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.
-
-Functions on Stacks
-~~~~~~~~~~~~~~~~~~~
-
-These are slightly tricky.
-
-::
-
- rest ( [1 ...] -- [...] )
-
- cons ( 1 [...] -- [1 ...] )
-
-``pop∘swap∘roll< rest``
-~~~~~~~~~~~~~~~~~~~~~~~
-
-::
-
- (1 2 3 0 -- 3 2 1) ([1 ...] -- [...])
-
-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):
-
-::
-
- (1 2 3 0 -- 3 2 1) ([4 ...] -- [...])
-
-Unify and update:
-
-::
-
- (1 2 3 0 -- 3 2 1) ([4 ...] -- [...])
- w/ {1: [4 ...]}
- ([4 ...] 2 3 0 -- 3 2 ) ( -- [...])
-
-Apply the first rule:
-
-::
-
- ([4 ...] 2 3 0 -- 3 2) (-- [...])
- ---------------------------------------
- ([4 ...] 2 3 0 -- 3 2 [...])
-
-And there we are.
-
-``pop∘swap∘roll<∘rest rest``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Let’s do it again.
-
-::
-
- ([4 ...] 2 3 0 -- 3 2 [...]) ([1 ...] -- [...])
-
-Re-label (the tails of the lists on each side each get their own label):
-
-::
-
- ([4 .0.] 2 3 0 -- 3 2 [.0.]) ([5 .1.] -- [.1.])
-
-Unify and update (note the opening square brackets have been omited in
-the substitution dict, this is deliberate and I’ll explain below):
-
-::
-
- ([4 .0.] 2 3 0 -- 3 2 [.0.] ) ([5 .1.] -- [.1.])
- w/ { .0.] : 5 .1.] }
- ([4 5 .1.] 2 3 0 -- 3 2 [5 .1.]) ([5 .1.] -- [.1.])
-
-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.
-
-Next we unify and find our two terms are the same already: ``[5 .1.]``:
-
-::
-
- ([4 5 .1.] 2 3 0 -- 3 2 [5 .1.]) ([5 .1.] -- [.1.])
-
-Giving us:
-
-::
-
- ([4 5 .1.] 2 3 0 -- 3 2) (-- [.1.])
-
-From here we apply the first rule and get:
-
-::
-
- ([4 5 .1.] 2 3 0 -- 3 2 [.1.])
-
-Cleaning up the labels:
-
-::
-
- ([4 5 ...] 2 3 1 -- 3 2 [...])
-
-This is the stack effect of ``pop∘swap∘roll<∘rest∘rest``.
-
-``pop∘swap∘roll<∘rest∘rest cons``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-::
-
- ([4 5 ...] 2 3 1 -- 3 2 [...]) (1 [...] -- [1 ...])
-
-Re-label:
-
-::
-
- ([4 5 .1.] 2 3 1 -- 3 2 [.1.]) (6 [.2.] -- [6 .2.])
-
-Unify:
-
-::
-
- ([4 5 .1.] 2 3 1 -- 3 2 [.1.]) (6 [.2.] -- [6 .2.])
- w/ { .1.] : .2.] }
- ([4 5 .2.] 2 3 1 -- 3 2 ) (6 -- [6 .2.])
- w/ {2: 6}
- ([4 5 .2.] 6 3 1 -- 3 ) ( -- [6 .2.])
-
-First rule:
-
-::
-
- ([4 5 .2.] 6 3 1 -- 3 [6 .2.])
-
-Re-label:
-
-::
-
- ([4 5 ...] 2 3 1 -- 3 [2 ...])
-
-Done.
-
-``pop∘swap∘roll<∘rest∘rest∘cons cons``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-One more time.
-
-::
-
- ([4 5 ...] 2 3 1 -- 3 [2 ...]) (1 [...] -- [1 ...])
-
-Re-label:
-
-::
-
- ([4 5 .1.] 2 3 1 -- 3 [2 .1.]) (6 [.2.] -- [6 .2.])
-
-Unify:
-
-::
-
- ([4 5 .1.] 2 3 1 -- 3 [2 .1.]) (6 [.2.] -- [6 .2.] )
- w/ { .2.] : 2 .1.] }
- ([4 5 .1.] 2 3 1 -- 3 ) (6 -- [6 2 .1.])
- w/ {3: 6}
- ([4 5 .1.] 2 6 1 -- ) ( -- [6 2 .1.])
-
-First or second rule:
-
-::
-
- ([4 5 .1.] 2 6 1 -- [6 2 .1.])
-
-Clean up the labels:
-
-::
-
- ([4 5 ...] 2 3 1 -- [3 2 ...])
-
-And there you have it, the stack effect for
-``pop∘swap∘roll<∘rest∘rest∘cons∘cons``.
-
-::
-
- ([4 5 ...] 2 3 1 -- [3 2 ...])
-
-From this stack effect comment it should be possible to construct the
-following Python code:
-
-.. code:: ipython2
-
- def F(stack):
- (_, (d, (c, ((a, (b, S0)), stack)))) = stack
- return (d, (c, S0)), stack
-
-Part II: Implementation
------------------------
-
-Representing Stack Effect Comments in Python
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-I’m going to use pairs of tuples of type descriptors, which will be
-integers or tuples of type descriptors:
-
-.. code:: ipython2
-
- roll_dn = (1, 2, 3), (2, 3, 1)
-
- pop = (1,), ()
-
- swap = (1, 2), (2, 1)
-
-``compose()``
-~~~~~~~~~~~~~
-
-.. code:: ipython2
-
- def compose(f, g):
-
- (f_in, f_out), (g_in, g_out) = f, g
-
- # First rule.
- #
- # (a -- b) (-- d)
- # ---------------------
- # (a -- b d)
-
- if not g_in:
-
- fg_in, fg_out = f_in, f_out + g_out
-
- # Second rule.
- #
- # (a --) (c -- d)
- # ---------------------
- # (c a -- d)
-
- elif not f_out:
-
- fg_in, fg_out = g_in + f_in, g_out
-
- else: # Unify, update, recur.
-
- fo, gi = f_out[-1], g_in[-1]
-
- s = unify(gi, fo)
-
- if s == False: # s can also be the empty dict, which is ok.
- raise TypeError('Cannot unify %r and %r.' % (fo, gi))
-
- f_g = (f_in, f_out[:-1]), (g_in[:-1], g_out)
-
- if s: f_g = update(s, f_g)
-
- fg_in, fg_out = compose(*f_g)
-
- return fg_in, fg_out
-
-``unify()``
-~~~~~~~~~~~
-
-.. code:: ipython2
-
- def unify(u, v, s=None):
- if s is None:
- s = {}
-
- if isinstance(u, int):
- s[u] = v
- elif isinstance(v, int):
- s[v] = u
- else:
- s = False
-
- return s
-
-``update()``
-~~~~~~~~~~~~
-
-.. code:: ipython2
-
- def update(s, term):
- if not isinstance(term, tuple):
- return s.get(term, term)
- return tuple(update(s, inner) for inner in term)
-
-``relabel()``
-~~~~~~~~~~~~~
-
-.. code:: ipython2
-
- def relabel(left, right):
- return left, _1000(right)
-
- def _1000(right):
- if not isinstance(right, tuple):
- return 1000 + right
- return tuple(_1000(n) for n in right)
-
- relabel(pop, swap)
-
-
-
-
-.. parsed-literal::
-
- (((1,), ()), ((1001, 1002), (1002, 1001)))
-
-
-
-``delabel()``
-~~~~~~~~~~~~~
-
-.. code:: ipython2
-
- def delabel(f):
- s = {u: i for i, u in enumerate(sorted(_unique(f)))}
- return update(s, f)
-
- def _unique(f, seen=None):
- if seen is None:
- seen = set()
- if not isinstance(f, tuple):
- seen.add(f)
- else:
- for inner in f:
- _unique(inner, seen)
- return seen
-
- delabel(relabel(pop, swap))
-
-
-
-
-.. parsed-literal::
-
- (((0,), ()), ((1, 2), (2, 1)))
-
-
-
-``C()``
-~~~~~~~
-
-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.)
-
-.. code:: ipython2
-
- def C(f, g):
- f, g = relabel(f, g)
- fg = compose(f, g)
- return delabel(fg)
-
-Let’s try it out.
-
-.. code:: ipython2
-
- C(pop, swap)
-
-
-
-
-.. parsed-literal::
-
- ((1, 2, 0), (2, 1))
-
-
-
-.. code:: ipython2
-
- C(C(pop, swap), roll_dn)
-
-
-
-
-.. parsed-literal::
-
- ((3, 1, 2, 0), (2, 1, 3))
-
-
-
-.. code:: ipython2
-
- C(swap, roll_dn)
-
-
-
-
-.. parsed-literal::
-
- ((2, 0, 1), (1, 0, 2))
-
-
-
-.. code:: ipython2
-
- C(pop, C(swap, roll_dn))
-
-
-
-
-.. parsed-literal::
-
- ((3, 1, 2, 0), (2, 1, 3))
-
-
-
-.. code:: ipython2
-
- poswrd = reduce(C, (pop, swap, roll_dn))
- poswrd
-
-
-
-
-.. parsed-literal::
-
- ((3, 1, 2, 0), (2, 1, 3))
-
-
-
-Stack Functions
-~~~~~~~~~~~~~~~
-
-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.
-
-.. code:: ipython2
-
- rest = ((1, 2),), (2,)
-
- cons = (1, 2), ((1, 2),)
-
-.. code:: ipython2
-
- C(poswrd, rest)
-
-
-
-
-.. parsed-literal::
-
- (((3, 4), 1, 2, 0), (2, 1, 4))
-
-
-
-Compare this to the stack effect comment we wrote above:
-
-::
-
- (( (3, 4), 1, 2, 0 ), ( 2, 1, 4 ))
- ( [4 ...] 2 3 0 -- 3 2 [...])
-
-The translation table, if you will, would be:
-
-::
-
- {
- 3: 4,
- 4: ...],
- 1: 2,
- 2: 3,
- 0: 0,
- }
-
-.. code:: ipython2
-
- F = reduce(C, (pop, swap, roll_dn, rest, rest, cons, cons))
-
- F
-
-
-
-
-.. parsed-literal::
-
- (((3, (4, 5)), 1, 2, 0), ((2, (1, 5)),))
-
-
-
-Compare with the stack effect comment and you can see it works fine:
-
-::
-
- ([4 5 ...] 2 3 1 -- [3 2 ...])
- 3 4 5 1 2 0 2 1 5
-
-Dealing with ``cons`` and ``uncons``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-However, if we try to compose e.g. ``cons`` and ``uncons`` it won’t
-work:
-
-.. code:: ipython2
-
- uncons = ((1, 2),), (1, 2)
-
-.. code:: ipython2
-
- try:
- C(cons, uncons)
- except Exception, e:
- print e
-
-
-.. parsed-literal::
-
- Cannot unify (1, 2) and (1001, 1002).
-
-
-``unify()`` version 2
-^^^^^^^^^^^^^^^^^^^^^
-
-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:
-
-.. code:: ipython2
-
- def unify(u, v, s=None):
- if s is None:
- s = {}
- elif s:
- u = update(s, u)
- v = update(s, v)
-
- if isinstance(u, int):
- s[u] = v
-
- elif isinstance(v, int):
- s[v] = u
-
- elif isinstance(u, tuple) and isinstance(v, tuple):
-
- if len(u) != 2 or len(v) != 2:
- # Not a type error, caller passed in a bad value.
- raise ValueError(repr((u, v))) # FIXME this message sucks.
-
- (a, b), (c, d) = u, v
- s = unify(a, c, s)
- if s != False:
- s = unify(b, d, s)
- else:
- s = False
-
- return s
-
-.. code:: ipython2
-
- C(cons, uncons)
-
-
-
-
-.. parsed-literal::
-
- ((0, 1), (0, 1))
-
-
-
-Part III: Compiling Yin Functions
----------------------------------
-
-Now consider the Python function we would like to derive:
-
-.. code:: ipython2
-
- def F_python(stack):
- (_, (d, (c, ((a, (b, S0)), stack)))) = stack
- return (d, (c, S0)), stack
-
-And compare it to the input stack effect comment tuple we just computed:
-
-.. code:: ipython2
-
- F[0]
-
-
-
-
-.. parsed-literal::
-
- ((3, (4, 5)), 1, 2, 0)
-
-
-
-The stack-de-structuring tuple has nearly the same form as our input
-stack effect comment tuple, just in the reverse order:
-
-::
-
- (_, (d, (c, ((a, (b, S0)), stack))))
-
-Remove the punctuation:
-
-::
-
- _ d c (a, (b, S0))
-
-Reverse the order and compare:
-
-::
-
- (a, (b, S0)) c d _
- ((3, (4, 5 )), 1, 2, 0)
-
-Eh?
-
-And the return tuple
-
-.. code:: ipython2
-
- F[1]
-
-
-
-
-.. parsed-literal::
-
- ((2, (1, 5)),)
-
-
-
-is similar to the output stack effect comment tuple:
-
-::
-
- ((d, (c, S0)), stack)
- ((2, (1, 5 )), )
-
-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.)
-
-Python Identifiers
-~~~~~~~~~~~~~~~~~~
-
-We want to substitute Python identifiers for the integers. I’m going to
-repurpose ``joy.parser.Symbol`` class for this:
-
-.. code:: ipython2
-
- from collections import defaultdict
- from joy.parser import Symbol
-
-
- def _names_for():
- I = iter(xrange(1000))
- return lambda: Symbol('a%i' % next(I))
-
-
- def identifiers(term, s=None):
- if s is None:
- s = defaultdict(_names_for())
- if isinstance(term, int):
- return s[term]
- return tuple(identifiers(inner, s) for inner in term)
-
-``doc_from_stack_effect()``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-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.
-
-.. code:: ipython2
-
- def doc_from_stack_effect(inputs, outputs):
- return '(%s--%s)' % (
- ' '.join(map(_to_str, inputs + ('',))),
- ' '.join(map(_to_str, ('',) + outputs))
- )
-
-
- def _to_str(term):
- if not isinstance(term, tuple):
- try:
- t = term.prefix == 's'
- except AttributeError:
- return str(term)
- return '[.%i.]' % term.number if t else str(term)
-
- a = []
- while term and isinstance(term, tuple):
- item, term = term
- a.append(_to_str(item))
-
- try:
- n = term.number
- except AttributeError:
- n = term
- else:
- if term.prefix != 's':
- raise ValueError('Stack label: %s' % (term,))
-
- a.append('.%s.' % (n,))
- return '[%s]' % ' '.join(a)
-
-``compile_()``
-~~~~~~~~~~~~~~
-
-Now we can write a compiler function to emit Python source code. (The
-underscore suffix distiguishes it from the built-in ``compile()``
-function.)
-
-.. code:: ipython2
-
- def compile_(name, f, doc=None):
- if doc is None:
- doc = doc_from_stack_effect(*f)
- inputs, outputs = identifiers(f)
- i = o = Symbol('stack')
- for term in inputs:
- i = term, i
- for term in outputs:
- o = term, o
- return '''def %s(stack):
- """%s"""
- %s = stack
- return %s''' % (name, doc, i, o)
-
-Here it is in action:
-
-.. code:: ipython2
-
- source = compile_('F', F)
-
- print source
-
-
-.. parsed-literal::
-
- def F(stack):
- """([3 4 .5.] 1 2 0 -- [2 1 .5.])"""
- (a5, (a4, (a3, ((a0, (a1, a2)), stack)))) = stack
- return ((a4, (a3, a2)), stack)
-
-
-Compare:
-
-.. code:: ipython2
-
- def F_python(stack):
- (_, (d, (c, ((a, (b, S0)), stack)))) = stack
- return ((d, (c, S0)), stack)
-
-Next steps:
-
-.. code:: ipython2
-
- L = {}
-
- eval(compile(source, '__main__', 'single'), {}, L)
-
- L['F']
-
-
-
-
-.. parsed-literal::
-
- <function F>
-
-
-
-Let’s try it out:
-
-.. code:: ipython2
-
- from notebook_preamble import D, J, V
- from joy.library import SimpleFunctionWrapper
-
-.. code:: ipython2
-
- D['F'] = SimpleFunctionWrapper(L['F'])
-
-.. code:: ipython2
-
- J('[4 5 ...] 2 3 1 F')
-
-
-.. parsed-literal::
-
- [3 2 ...]
-
-
-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.)
-
-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.
-
-Compiling Library Functions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We can use ``compile_()`` to generate many primitives in the library
-from their stack effect comments:
-
-.. code:: ipython2
-
- def defs():
-
- rolldown = (1, 2, 3), (2, 3, 1)
-
- rollup = (1, 2, 3), (3, 1, 2)
-
- pop = (1,), ()
-
- swap = (1, 2), (2, 1)
-
- rest = ((1, 2),), (2,)
-
- rrest = C(rest, rest)
-
- cons = (1, 2), ((1, 2),)
-
- uncons = ((1, 2),), (1, 2)
-
- swons = C(swap, cons)
-
- return locals()
-
-.. code:: ipython2
-
- for name, stack_effect_comment in sorted(defs().items()):
- print
- print compile_(name, stack_effect_comment)
- print
-
-
-.. parsed-literal::
-
-
- def cons(stack):
- """(1 2 -- [1 .2.])"""
- (a1, (a0, stack)) = stack
- return ((a0, a1), stack)
-
-
- def pop(stack):
- """(1 --)"""
- (a0, stack) = stack
- return stack
-
-
- def rest(stack):
- """([1 .2.] -- 2)"""
- ((a0, a1), stack) = stack
- return (a1, stack)
-
-
- def rolldown(stack):
- """(1 2 3 -- 2 3 1)"""
- (a2, (a1, (a0, stack))) = stack
- return (a0, (a2, (a1, stack)))
-
-
- def rollup(stack):
- """(1 2 3 -- 3 1 2)"""
- (a2, (a1, (a0, stack))) = stack
- return (a1, (a0, (a2, stack)))
-
-
- def rrest(stack):
- """([0 1 .2.] -- 2)"""
- ((a0, (a1, a2)), stack) = stack
- return (a2, stack)
-
-
- def swap(stack):
- """(1 2 -- 2 1)"""
- (a1, (a0, stack)) = stack
- return (a0, (a1, stack))
-
-
- def swons(stack):
- """(0 1 -- [1 .0.])"""
- (a1, (a0, stack)) = stack
- return ((a1, a0), stack)
-
-
- def uncons(stack):
- """([1 .2.] -- 1 2)"""
- ((a0, a1), stack) = stack
- return (a1, (a0, stack))
-
-
-
-Part IV: Types and Subtypes of Arguments
-----------------------------------------
-
-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.
-
-“Number” Type
-~~~~~~~~~~~~~
-
-Consider the definition of ``sqr``:
-
-::
-
- sqr == dup mul
-
-The ``dup`` function accepts one *anything* and returns two of that:
-
-::
-
- dup (1 -- 1 1)
-
-And ``mul`` accepts two “numbers” (we’re ignoring ints vs. floats
-vs. complex, etc., for now) and returns just one:
-
-::
-
- mul (n n -- n)
-
-So we’re composing:
-
-::
-
- (1 -- 1 1)∘(n n -- n)
-
-The rules say we unify 1 with ``n``:
-
-::
-
- (1 -- 1 1)∘(n n -- n)
- --------------------------- w/ {1: n}
- (1 -- 1 )∘(n -- n)
-
-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:
-
-::
-
- (n n -- n)∘(1 -- 1 1)
- --------------------------- w/ {1: n}
- (n n -- )∘( -- n n)
-
-The important thing here is that the mapping is going the same way in
-both cases, from the “any” integer to the number
-
-Distinguishing Numbers
-~~~~~~~~~~~~~~~~~~~~~~
-
-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:
-
-::
-
- mul (n2 n1 -- n3)
-
-
- (1 -- 1 1)∘(n2 n1 -- n3)
- -------------------------------- w/ {1: n2}
- (n2 -- n2 )∘(n2 -- n3)
-
-
- (n2 n1 -- n3)∘(1 -- 1 1 )
- -------------------------------- w/ {1: n3}
- (n2 n1 -- )∘( -- n3 n3)
-
-Distinguishing Types
-~~~~~~~~~~~~~~~~~~~~
-
-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?
-
-::
-
- (a -- b t[i])∘(c u[j] -- d) t <= u (t is subtype of u)
- -------------------------------
- (a -- b )∘(c -- d) t[i] == t[k] == u[j]
- ^
-
- (a -- b t[i])∘(c u[j] -- d) u <= t (u is subtype of t)
- -------------------------------
- (a -- b )∘(c -- d) t[i] == u[k] == u[j]
-
-The indices ``i``, ``k``, and ``j`` are the number part of our labels
-and ``t`` and ``u`` are the domains.
-
-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.
-
-.. code:: ipython2
-
- class AnyJoyType(object):
-
- prefix = 'a'
-
- def __init__(self, number):
- self.number = number
-
- def __repr__(self):
- return self.prefix + str(self.number)
-
- def __eq__(self, other):
- return (
- isinstance(other, self.__class__)
- and other.prefix == self.prefix
- and other.number == self.number
- )
-
- def __ge__(self, other):
- return issubclass(other.__class__, self.__class__)
-
- def __add__(self, other):
- return self.__class__(self.number + other)
- __radd__ = __add__
-
- def __hash__(self):
- return hash(repr(self))
-
-
- class NumberJoyType(AnyJoyType): prefix = 'n'
- class FloatJoyType(NumberJoyType): prefix = 'f'
- class IntJoyType(FloatJoyType): prefix = 'i'
-
-
- class StackJoyType(AnyJoyType):
- prefix = 's'
-
-
- _R = range(10)
- A = map(AnyJoyType, _R)
- N = map(NumberJoyType, _R)
- S = map(StackJoyType, _R)
-
-Mess with it a little:
-
-.. code:: ipython2
-
- from itertools import permutations
-
-“Any” types can be specialized to numbers and stacks, but not vice
-versa:
-
-.. code:: ipython2
-
- for a, b in permutations((A[0], N[0], S[0]), 2):
- print a, '>=', b, '->', a >= b
-
-
-.. parsed-literal::
-
- a0 >= n0 -> True
- a0 >= s0 -> True
- n0 >= a0 -> False
- n0 >= s0 -> False
- s0 >= a0 -> False
- s0 >= n0 -> False
-
-
-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):
-
-.. code:: ipython2
-
- for a, b in permutations((A[0], N[0], FloatJoyType(0), IntJoyType(0)), 2):
- print a, '>=', b, '->', a >= b
-
-
-.. parsed-literal::
-
- a0 >= n0 -> True
- a0 >= f0 -> True
- a0 >= i0 -> True
- n0 >= a0 -> False
- n0 >= f0 -> True
- n0 >= i0 -> True
- f0 >= a0 -> False
- f0 >= n0 -> False
- f0 >= i0 -> True
- i0 >= a0 -> False
- i0 >= n0 -> False
- i0 >= f0 -> False
-
-
-Typing ``sqr``
-~~~~~~~~~~~~~~
-
-.. code:: ipython2
-
- dup = (A[1],), (A[1], A[1])
-
- mul = (N[1], N[2]), (N[3],)
-
-.. code:: ipython2
-
- dup
-
-
-
-
-.. parsed-literal::
-
- ((a1,), (a1, a1))
-
-
-
-.. code:: ipython2
-
- mul
-
-
-
-
-.. parsed-literal::
-
- ((n1, n2), (n3,))
-
-
-
-Modifying the Inferencer
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-Re-labeling still works fine:
-
-.. code:: ipython2
-
- foo = relabel(dup, mul)
-
- foo
-
-
-
-
-.. parsed-literal::
-
- (((a1,), (a1, a1)), ((n1001, n1002), (n1003,)))
-
-
-
-``delabel()`` version 2
-^^^^^^^^^^^^^^^^^^^^^^^
-
-The ``delabel()`` function needs an overhaul. It now has to keep track
-of how many labels of each domain it has “seen”.
-
-.. code:: ipython2
-
- from collections import Counter
-
-
- def delabel(f, seen=None, c=None):
- if seen is None:
- assert c is None
- seen, c = {}, Counter()
-
- try:
- return seen[f]
- except KeyError:
- pass
-
- if not isinstance(f, tuple):
- seen[f] = f.__class__(c[f.prefix] + 1)
- c[f.prefix] += 1
- return seen[f]
-
- return tuple(delabel(inner, seen, c) for inner in f)
-
-.. code:: ipython2
-
- delabel(foo)
-
-
-
-
-.. parsed-literal::
-
- (((a1,), (a1, a1)), ((n1, n2), (n3,)))
-
-
-
-``unify()`` version 3
-^^^^^^^^^^^^^^^^^^^^^
-
-.. code:: ipython2
-
- def unify(u, v, s=None):
- if s is None:
- s = {}
- elif s:
- u = update(s, u)
- v = update(s, v)
-
- if u == v:
- return s
-
- if isinstance(u, AnyJoyType) and isinstance(v, AnyJoyType):
- if u >= v:
- s[u] = v
- return s
- if v >= u:
- s[v] = u
- return s
- raise TypeError('Cannot unify %r and %r.' % (u, v))
-
- if isinstance(u, tuple) and isinstance(v, tuple):
- if len(u) != len(v) != 2:
- raise TypeError(repr((u, v)))
- for uu, vv in zip(u, v):
- s = unify(uu, vv, s)
- if s == False: # (instead of a substitution dict.)
- break
- return s
-
- if isinstance(v, tuple):
- if not stacky(u):
- raise TypeError('Cannot unify %r and %r.' % (u, v))
- s[u] = v
- return s
-
- if isinstance(u, tuple):
- if not stacky(v):
- raise TypeError('Cannot unify %r and %r.' % (v, u))
- s[v] = u
- return s
-
- return False
-
-
- def stacky(thing):
- return thing.__class__ in {AnyJoyType, StackJoyType}
-
-Rewrite the stack effect comments:
-
-.. code:: ipython2
-
- def defs():
-
- rolldown = (A[1], A[2], A[3]), (A[2], A[3], A[1])
-
- rollup = (A[1], A[2], A[3]), (A[3], A[1], A[2])
-
- pop = (A[1],), ()
-
- popop = (A[2], A[1],), ()
-
- popd = (A[2], A[1],), (A[1],)
-
- popdd = (A[3], A[2], A[1],), (A[2], A[1],)
-
- swap = (A[1], A[2]), (A[2], A[1])
-
- rest = ((A[1], S[1]),), (S[1],)
-
- rrest = C(rest, rest)
-
- cons = (A[1], S[1]), ((A[1], S[1]),)
-
- ccons = C(cons, cons)
-
- uncons = ((A[1], S[1]),), (A[1], S[1])
-
- swons = C(swap, cons)
-
- dup = (A[1],), (A[1], A[1])
-
- dupd = (A[2], A[1]), (A[2], A[2], A[1])
-
- mul = (N[1], N[2]), (N[3],)
-
- sqrt = C(dup, mul)
-
- first = ((A[1], S[1]),), (A[1],)
-
- second = C(rest, first)
-
- third = C(rest, second)
-
- tuck = (A[2], A[1]), (A[1], A[2], A[1])
-
- over = (A[2], A[1]), (A[2], A[1], A[2])
-
- succ = pred = (N[1],), (N[2],)
-
- divmod_ = pm = (N[2], N[1]), (N[4], N[3])
-
- return locals()
-
-.. code:: ipython2
-
- DEFS = defs()
-
-.. code:: ipython2
-
- for name, stack_effect_comment in sorted(DEFS.items()):
- print name, '=', doc_from_stack_effect(*stack_effect_comment)
-
-
-.. parsed-literal::
-
- ccons = (a1 a2 [.1.] -- [a1 a2 .1.])
- cons = (a1 [.1.] -- [a1 .1.])
- divmod_ = (n2 n1 -- n4 n3)
- dup = (a1 -- a1 a1)
- dupd = (a2 a1 -- a2 a2 a1)
- first = ([a1 .1.] -- a1)
- mul = (n1 n2 -- n3)
- over = (a2 a1 -- a2 a1 a2)
- pm = (n2 n1 -- n4 n3)
- pop = (a1 --)
- popd = (a2 a1 -- a1)
- popdd = (a3 a2 a1 -- a2 a1)
- popop = (a2 a1 --)
- pred = (n1 -- n2)
- rest = ([a1 .1.] -- [.1.])
- rolldown = (a1 a2 a3 -- a2 a3 a1)
- rollup = (a1 a2 a3 -- a3 a1 a2)
- rrest = ([a1 a2 .1.] -- [.1.])
- second = ([a1 a2 .1.] -- a2)
- sqrt = (n1 -- n2)
- succ = (n1 -- n2)
- swap = (a1 a2 -- a2 a1)
- swons = ([.1.] a1 -- [a1 .1.])
- third = ([a1 a2 a3 .1.] -- a3)
- tuck = (a2 a1 -- a1 a2 a1)
- uncons = ([a1 .1.] -- a1 [.1.])
-
-
-.. code:: ipython2
-
- globals().update(DEFS)
-
-Compose ``dup`` and ``mul``
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code:: ipython2
-
- C(dup, mul)
-
-
-
-
-.. parsed-literal::
-
- ((n1,), (n2,))
-
-
-
-Revisit the ``F`` function, works fine.
-
-.. code:: ipython2
-
- F = reduce(C, (pop, swap, rolldown, rest, rest, cons, cons))
- F
-
-
-
-
-.. parsed-literal::
-
- (((a1, (a2, s1)), a3, a4, a5), ((a4, (a3, s1)),))
-
-
-
-.. code:: ipython2
-
- print doc_from_stack_effect(*F)
-
-
-.. parsed-literal::
-
- ([a1 a2 .1.] a3 a4 a5 -- [a4 a3 .1.])
-
-
-Some otherwise inefficient functions are no longer to be feared. We can
-also get the effect of combinators in some limited cases.
-
-.. code:: ipython2
-
- def neato(*funcs):
- print doc_from_stack_effect(*reduce(C, funcs))
-
-.. code:: ipython2
-
- # e.g. [swap] dip
- neato(rollup, swap, rolldown)
-
-
-.. parsed-literal::
-
- (a1 a2 a3 -- a2 a1 a3)
-
-
-.. code:: ipython2
-
- # e.g. [popop] dipd
- neato(popdd, rolldown, pop)
-
-
-.. parsed-literal::
-
- (a1 a2 a3 a4 -- a3 a4)
-
-
-.. code:: ipython2
-
- # Reverse the order of the top three items.
- neato(rollup, swap)
-
-
-.. parsed-literal::
-
- (a1 a2 a3 -- a3 a2 a1)
-
-
-``compile_()`` version 2
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-Because the type labels represent themselves as valid Python identifiers
-the ``compile_()`` function doesn’t need to generate them anymore:
-
-.. code:: ipython2
-
- def compile_(name, f, doc=None):
- inputs, outputs = f
- if doc is None:
- doc = doc_from_stack_effect(inputs, outputs)
- i = o = Symbol('stack')
- for term in inputs:
- i = term, i
- for term in outputs:
- o = term, o
- return '''def %s(stack):
- """%s"""
- %s = stack
- return %s''' % (name, doc, i, o)
-
-.. code:: ipython2
-
- print compile_('F', F)
-
-
-.. parsed-literal::
-
- def F(stack):
- """([a1 a2 .1.] a3 a4 a5 -- [a4 a3 .1.])"""
- (a5, (a4, (a3, ((a1, (a2, s1)), stack)))) = stack
- return ((a4, (a3, s1)), stack)
-
-
-But it cannot magically create new functions that involve e.g. math and
-such. Note that this is *not* a ``sqr`` function implementation:
-
-.. code:: ipython2
-
- print compile_('sqr', C(dup, mul))
-
-
-.. parsed-literal::
-
- def sqr(stack):
- """(n1 -- n2)"""
- (n1, stack) = stack
- return (n2, stack)
-
-
-(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.)
-
-``compilable()``
-^^^^^^^^^^^^^^^^
-
-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:
-
-.. code:: ipython2
-
- from itertools import imap
-
-
- def compilable(f):
- return isinstance(f, tuple) and all(imap(compilable, f)) or stacky(f)
-
-.. code:: ipython2
-
- for name, stack_effect_comment in sorted(defs().items()):
- if compilable(stack_effect_comment):
- print name, '=', doc_from_stack_effect(*stack_effect_comment)
-
-
-.. parsed-literal::
-
- ccons = (a1 a2 [.1.] -- [a1 a2 .1.])
- cons = (a1 [.1.] -- [a1 .1.])
- dup = (a1 -- a1 a1)
- dupd = (a2 a1 -- a2 a2 a1)
- first = ([a1 .1.] -- a1)
- over = (a2 a1 -- a2 a1 a2)
- pop = (a1 --)
- popd = (a2 a1 -- a1)
- popdd = (a3 a2 a1 -- a2 a1)
- popop = (a2 a1 --)
- rest = ([a1 .1.] -- [.1.])
- rolldown = (a1 a2 a3 -- a2 a3 a1)
- rollup = (a1 a2 a3 -- a3 a1 a2)
- rrest = ([a1 a2 .1.] -- [.1.])
- second = ([a1 a2 .1.] -- a2)
- swap = (a1 a2 -- a2 a1)
- swons = ([.1.] a1 -- [a1 .1.])
- third = ([a1 a2 a3 .1.] -- a3)
- tuck = (a2 a1 -- a1 a2 a1)
- uncons = ([a1 .1.] -- a1 [.1.])
-
-
-Part V: Functions that use the Stack
-------------------------------------
-
-Consider the ``stack`` function which grabs the whole stack, quotes it,
-and puts it on itself:
-
-::
-
- stack (... -- ... [...] )
- stack (... a -- ... a [a ...] )
- stack (... b a -- ... b a [a b ...])
-
-We would like to represent this in Python somehow. To do this we use a
-simple, elegant trick.
-
-::
-
- stack S -- ( S, S)
- stack (a, S) -- ( (a, S), (a, S))
- stack (a, (b, S)) -- ( (a, (b, S)), (a, (b, S)))
-
-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.
-
-``stack∘uncons``
-~~~~~~~~~~~~~~~~
-
-Let’s try composing ``stack`` and ``uncons``. We want this result:
-
-::
-
- stack∘uncons (... a -- ... a a [...])
-
-The stack effects are:
-
-::
-
- stack = S -- (S, S)
-
- uncons = ((a, Z), S) -- (Z, (a, S))
-
-Unifying:
-
-::
-
- S -- (S, S) ∘ ((a, Z), S) -- (Z, (a, S ))
- w/ { S: (a, Z) }
- (a, Z) -- ∘ -- (Z, (a, (a, Z)))
-
-So:
-
-::
-
- stack∘uncons == (a, Z) -- (Z, (a, (a, Z)))
-
-It works.
-
-``stack∘uncons∘uncons``
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Let’s try ``stack∘uncons∘uncons``:
-
-::
-
- (a, S ) -- (S, (a, (a, S ))) ∘ ((b, Z), S` ) -- (Z, (b, S` ))
-
- w/ { S: (b, Z) }
-
- (a, (b, Z)) -- ((b, Z), (a, (a, (b, Z)))) ∘ ((b, Z), S` ) -- (Z, (b, S` ))
-
- w/ { S`: (a, (a, (b, Z))) }
-
- (a, (b, Z)) -- ((b, Z), (a, (a, (b, Z)))) ∘ ((b, Z), (a, (a, (b, Z)))) -- (Z, (b, (a, (a, (b, Z)))))
-
- (a, (b, Z)) -- (Z, (b, (a, (a, (b, Z)))))
-
-It works.
-
-``compose()`` version 2
-^^^^^^^^^^^^^^^^^^^^^^^
-
-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.)
-
-.. code:: ipython2
-
- def compose(f, g):
- (f_in, f_out), (g_in, g_out) = f, g
- s = unify(g_in, f_out)
- if s == False: # s can also be the empty dict, which is ok.
- raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
- return update(s, (f_in, g_out))
-
-I don’t want to rewrite all the defs myself, so I’ll write a little
-conversion function instead. This is programmer’s laziness.
-
-.. code:: ipython2
-
- def sequence_to_stack(seq, stack=StackJoyType(23)):
- for item in seq: stack = item, stack
- return stack
-
- NEW_DEFS = {
- name: (sequence_to_stack(i), sequence_to_stack(o))
- for name, (i, o) in DEFS.iteritems()
- }
- NEW_DEFS['stack'] = S[0], (S[0], S[0])
- NEW_DEFS['swaack'] = (S[1], S[0]), (S[0], S[1])
- globals().update(NEW_DEFS)
-
-.. code:: ipython2
-
- C(stack, uncons)
-
-
-
-
-.. parsed-literal::
-
- ((a1, s1), (s1, (a1, (a1, s1))))
-
-
-
-.. code:: ipython2
-
- reduce(C, (stack, uncons, uncons))
-
-
-
-
-.. parsed-literal::
-
- ((a1, (a2, s1)), (s1, (a2, (a1, (a1, (a2, s1))))))
-
-
-
-The display function should be changed too.
-
-``doc_from_stack_effect()`` version 2
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Clunky junk, but it will suffice for now.
-
-.. code:: ipython2
-
- def doc_from_stack_effect(inputs, outputs):
- switch = [False] # Do we need to display the '...' for the rest of the main stack?
- i, o = _f(inputs, switch), _f(outputs, switch)
- if switch[0]:
- i.append('...')
- o.append('...')
- return '(%s--%s)' % (
- ' '.join(reversed([''] + i)),
- ' '.join(reversed(o + [''])),
- )
-
-
- def _f(term, switch):
- a = []
- while term and isinstance(term, tuple):
- item, term = term
- a.append(item)
- assert isinstance(term, StackJoyType), repr(term)
- a = [_to_str(i, term, switch) for i in a]
- return a
-
-
- def _to_str(term, stack, switch):
- if not isinstance(term, tuple):
- if term == stack:
- switch[0] = True
- return '[...]'
- return (
- '[.%i.]' % term.number
- if isinstance(term, StackJoyType)
- else str(term)
- )
-
- a = []
- while term and isinstance(term, tuple):
- item, term = term
- a.append(_to_str(item, stack, switch))
- assert isinstance(term, StackJoyType), repr(term)
- if term == stack:
- switch[0] = True
- end = '...'
- else:
- end = '.%i.' % term.number
- a.append(end)
- return '[%s]' % ' '.join(a)
-
-.. code:: ipython2
-
- for name, stack_effect_comment in sorted(NEW_DEFS.items()):
- print name, '=', doc_from_stack_effect(*stack_effect_comment)
-
-
-.. parsed-literal::
-
- ccons = (a1 a2 [.1.] -- [a1 a2 .1.])
- cons = (a1 [.1.] -- [a1 .1.])
- divmod_ = (n2 n1 -- n4 n3)
- dup = (a1 -- a1 a1)
- dupd = (a2 a1 -- a2 a2 a1)
- first = ([a1 .1.] -- a1)
- mul = (n1 n2 -- n3)
- over = (a2 a1 -- a2 a1 a2)
- pm = (n2 n1 -- n4 n3)
- pop = (a1 --)
- popd = (a2 a1 -- a1)
- popdd = (a3 a2 a1 -- a2 a1)
- popop = (a2 a1 --)
- pred = (n1 -- n2)
- rest = ([a1 .1.] -- [.1.])
- rolldown = (a1 a2 a3 -- a2 a3 a1)
- rollup = (a1 a2 a3 -- a3 a1 a2)
- rrest = ([a1 a2 .1.] -- [.1.])
- second = ([a1 a2 .1.] -- a2)
- sqrt = (n1 -- n2)
- stack = (... -- ... [...])
- succ = (n1 -- n2)
- swaack = ([.1.] -- [.0.])
- swap = (a1 a2 -- a2 a1)
- swons = ([.1.] a1 -- [a1 .1.])
- third = ([a1 a2 a3 .1.] -- a3)
- tuck = (a2 a1 -- a1 a2 a1)
- uncons = ([a1 .1.] -- a1 [.1.])
-
-
-.. code:: ipython2
-
- print ; print doc_from_stack_effect(*stack)
- print ; print doc_from_stack_effect(*C(stack, uncons))
- print ; print doc_from_stack_effect(*reduce(C, (stack, uncons, uncons)))
- print ; print doc_from_stack_effect(*reduce(C, (stack, uncons, cons)))
-
-
-.. parsed-literal::
-
-
- (... -- ... [...])
-
- (... a1 -- ... a1 a1 [...])
-
- (... a2 a1 -- ... a2 a1 a1 a2 [...])
-
- (... a1 -- ... a1 [a1 ...])
-
-
-.. code:: ipython2
-
- print doc_from_stack_effect(*C(ccons, stack))
-
-
-.. parsed-literal::
-
- (... a2 a1 [.1.] -- ... [a2 a1 .1.] [[a2 a1 .1.] ...])
-
-
-.. code:: ipython2
-
- Q = C(ccons, stack)
-
- Q
-
-
-
-
-.. parsed-literal::
-
- ((s1, (a1, (a2, s2))), (((a2, (a1, s1)), s2), ((a2, (a1, s1)), s2)))
-
-
-
-``compile_()`` version 3
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-This makes the ``compile_()`` function pretty simple as the stack effect
-comments are now already in the form needed for the Python code:
-
-.. code:: ipython2
-
- def compile_(name, f, doc=None):
- i, o = f
- if doc is None:
- doc = doc_from_stack_effect(i, o)
- return '''def %s(stack):
- """%s"""
- %s = stack
- return %s''' % (name, doc, i, o)
-
-.. code:: ipython2
-
- print compile_('Q', Q)
-
-
-.. parsed-literal::
-
- def Q(stack):
- """(... a2 a1 [.1.] -- ... [a2 a1 .1.] [[a2 a1 .1.] ...])"""
- (s1, (a1, (a2, s2))) = stack
- return (((a2, (a1, s1)), s2), ((a2, (a1, s1)), s2))
-
-
-
-
-
-
-
-.. code:: ipython2
-
- unstack = (S[1], S[0]), S[1]
- enstacken = S[0], (S[0], S[1])
-
-.. code:: ipython2
-
- print doc_from_stack_effect(*unstack)
-
-
-.. parsed-literal::
-
- ([.1.] --)
-
-
-.. code:: ipython2
-
- print doc_from_stack_effect(*enstacken)
-
-
-.. parsed-literal::
-
- (-- [.0.])
-
-
-.. code:: ipython2
-
- print doc_from_stack_effect(*C(cons, unstack))
-
-
-.. parsed-literal::
-
- (a1 [.1.] -- a1)
-
-
-.. code:: ipython2
-
- print doc_from_stack_effect(*C(cons, enstacken))
-
-
-.. parsed-literal::
-
- (a1 [.1.] -- [[a1 .1.] .2.])
-
-
-.. code:: ipython2
-
- C(cons, unstack)
-
-
-
-
-.. parsed-literal::
-
- ((s1, (a1, s2)), (a1, s1))
-
-
-
-
-Part VI: Multiple Stack Effects
--------------------------------
-
-…
-
-.. code:: ipython2
-
- class IntJoyType(NumberJoyType): prefix = 'i'
-
-
- F = map(FloatJoyType, _R)
- I = map(IntJoyType, _R)
-
-.. code:: ipython2
-
- muls = [
- ((I[2], (I[1], S[0])), (I[3], S[0])),
- ((F[2], (I[1], S[0])), (F[3], S[0])),
- ((I[2], (F[1], S[0])), (F[3], S[0])),
- ((F[2], (F[1], S[0])), (F[3], S[0])),
- ]
-
-.. code:: ipython2
-
- for f in muls:
- print doc_from_stack_effect(*f)
-
-
-.. parsed-literal::
-
- (i1 i2 -- i3)
- (i1 f2 -- f3)
- (f1 i2 -- f3)
- (f1 f2 -- f3)
-
-
-.. code:: ipython2
-
- for f in muls:
- try:
- e = C(dup, f)
- except TypeError:
- continue
- print doc_from_stack_effect(*dup), doc_from_stack_effect(*f), doc_from_stack_effect(*e)
-
-
-.. parsed-literal::
-
- (a1 -- a1 a1) (i1 i2 -- i3) (i1 -- i2)
- (a1 -- a1 a1) (f1 f2 -- f3) (f1 -- f2)
-
-
-.. code:: ipython2
-
- from itertools import product
-
-
- def meta_compose(F, G):
- for f, g in product(F, G):
- try:
- yield C(f, g)
- except TypeError:
- pass
-
-
- def MC(F, G):
- return sorted(set(meta_compose(F, G)))
-
-.. code:: ipython2
-
- for f in MC([dup], [mul]):
- print doc_from_stack_effect(*f)
-
-
-.. parsed-literal::
-
- (n1 -- n2)
-
-
-.. code:: ipython2
-
- for f in MC([dup], muls):
- print doc_from_stack_effect(*f)
-
-
-.. parsed-literal::
-
- (f1 -- f2)
- (i1 -- i2)
-
-
-Representing an Unbounded Sequence of Types
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-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:
-
-::
-
- A*
-
-The ``A*`` works by splitting the universe into two alternate histories:
-
-::
-
- A* -> 0 | A A*
-
-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.
-
-Consider unifying two stacks (the lowercase letters are any type
-variables of the kinds we have defined so far):
-
-::
-
- [a A* b .0.] U [c d .1.]
- w/ {c: a}
- [ A* b .0.] U [ d .1.]
-
-Now we have to split universes to unify ``A*``. In the first universe it
-disappears:
-
-::
-
- [b .0.] U [d .1.]
- w/ {d: b, .1.: .0.}
- [] U []
-
-While in the second it spawns an ``A``, which we will label ``e``:
-
-::
-
- [e A* b .0.] U [d .1.]
- w/ {d: e}
- [ A* b .0.] U [ .1.]
- w/ {.1.: A* b .0.}
- [ A* b .0.] U [ A* b .0.]
-
-Giving us two unifiers:
-
-::
-
- {c: a, d: b, .1.: .0.}
- {c: a, d: e, .1.: A* b .0.}
-
-.. code:: ipython2
-
- class KleeneStar(object):
-
- kind = AnyJoyType
-
- def __init__(self, number):
- self.number = number
- self.count = 0
- self.prefix = repr(self)
-
- def __repr__(self):
- return '%s%i*' % (self.kind.prefix, self.number)
-
- def another(self):
- self.count += 1
- return self.kind(10000 * self.number + self.count)
-
- def __eq__(self, other):
- return (
- isinstance(other, self.__class__)
- and other.number == self.number
- )
-
- def __ge__(self, other):
- return self.kind >= other.kind
-
- def __add__(self, other):
- return self.__class__(self.number + other)
- __radd__ = __add__
-
- def __hash__(self):
- return hash(repr(self))
-
- class AnyStarJoyType(KleeneStar): kind = AnyJoyType
- class NumberStarJoyType(KleeneStar): kind = NumberJoyType
- #class FloatStarJoyType(KleeneStar): kind = FloatJoyType
- #class IntStarJoyType(KleeneStar): kind = IntJoyType
- class StackStarJoyType(KleeneStar): kind = StackJoyType
-
-
- As = map(AnyStarJoyType, _R)
- Ns = map(NumberStarJoyType, _R)
- Ss = map(StackStarJoyType, _R)
-
-``unify()`` version 4
-^^^^^^^^^^^^^^^^^^^^^
-
-Can now return multiple results…
-
-.. code:: ipython2
-
- def unify(u, v, s=None):
- if s is None:
- s = {}
- elif s:
- u = update(s, u)
- v = update(s, v)
-
- if u == v:
- return s,
-
- if isinstance(u, AnyJoyType) and isinstance(v, AnyJoyType):
- if u >= v:
- s[u] = v
- return s,
- if v >= u:
- s[v] = u
- return s,
- raise TypeError('Cannot unify %r and %r.' % (u, v))
-
- if isinstance(u, tuple) and isinstance(v, tuple):
- if len(u) != len(v) != 2:
- raise TypeError(repr((u, v)))
-
- a, b = v
- if isinstance(a, KleeneStar):
- # Two universes, in one the Kleene star disappears and unification
- # continues without it...
- s0 = unify(u, b)
-
- # In the other it spawns a new variable.
- s1 = unify(u, (a.another(), v))
-
- t = s0 + s1
- for sn in t:
- sn.update(s)
- return t
-
- a, b = u
- if isinstance(a, KleeneStar):
- s0 = unify(v, b)
- s1 = unify(v, (a.another(), u))
- t = s0 + s1
- for sn in t:
- sn.update(s)
- return t
-
- ses = unify(u[0], v[0], s)
- results = ()
- for sn in ses:
- results += unify(u[1], v[1], sn)
- return results
-
- if isinstance(v, tuple):
- if not stacky(u):
- raise TypeError('Cannot unify %r and %r.' % (u, v))
- s[u] = v
- return s,
-
- if isinstance(u, tuple):
- if not stacky(v):
- raise TypeError('Cannot unify %r and %r.' % (v, u))
- s[v] = u
- return s,
-
- return ()
-
-
- def stacky(thing):
- return thing.__class__ in {AnyJoyType, StackJoyType}
-
-.. code:: ipython2
-
- a = (As[1], S[1])
- a
-
-
-
-
-.. parsed-literal::
-
- (a1*, s1)
-
-
-
-.. code:: ipython2
-
- b = (A[1], S[2])
- b
-
-
-
-
-.. parsed-literal::
-
- (a1, s2)
-
-
-
-.. code:: ipython2
-
- for result in unify(b, a):
- print result, '->', update(result, a), update(result, b)
-
-
-.. parsed-literal::
-
- {s1: (a1, s2)} -> (a1*, (a1, s2)) (a1, s2)
- {a1: a10001, s2: (a1*, s1)} -> (a1*, s1) (a10001, (a1*, s1))
-
-
-.. code:: ipython2
-
- for result in unify(a, b):
- print result, '->', update(result, a), update(result, b)
-
-
-.. parsed-literal::
-
- {s1: (a1, s2)} -> (a1*, (a1, s2)) (a1, s2)
- {a1: a10002, s2: (a1*, s1)} -> (a1*, s1) (a10002, (a1*, s1))
-
-
-::
-
- (a1*, s1) [a1*] (a1, s2) [a1]
-
- (a1*, (a1, s2)) [a1* a1] (a1, s2) [a1]
-
- (a1*, s1) [a1*] (a2, (a1*, s1)) [a2 a1*]
-
-.. code:: ipython2
-
- sum_ = ((Ns[1], S[1]), S[0]), (N[0], S[0])
-
- print doc_from_stack_effect(*sum_)
-
-
-.. parsed-literal::
-
- ([n1* .1.] -- n0)
-
-
-.. code:: ipython2
-
- f = (N[1], (N[2], (N[3], S[1]))), S[0]
-
- print doc_from_stack_effect(S[0], f)
-
-
-.. parsed-literal::
-
- (-- [n1 n2 n3 .1.])
-
-
-.. code:: ipython2
-
- for result in unify(sum_[0], f):
- print result, '->', update(result, sum_[1])
-
-
-.. parsed-literal::
-
- {s1: (n1, (n2, (n3, s1)))} -> (n0, s0)
- {n1: n10001, s1: (n2, (n3, s1))} -> (n0, s0)
- {n1: n10001, s1: (n3, s1), n2: n10002} -> (n0, s0)
- {n1: n10001, s1: (n1*, s1), n3: n10003, n2: n10002} -> (n0, s0)
-
-
-``compose()`` version 3
-^^^^^^^^^^^^^^^^^^^^^^^
-
-This function has to be modified to yield multiple results.
-
-.. code:: ipython2
-
- def compose(f, g):
- (f_in, f_out), (g_in, g_out) = f, g
- s = unify(g_in, f_out)
- if not s:
- raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
- for result in s:
- yield update(result, (f_in, g_out))
-
-
-
-.. code:: ipython2
-
- def meta_compose(F, G):
- for f, g in product(F, G):
- try:
- for result in C(f, g):
- yield result
- except TypeError:
- pass
-
-
- def C(f, g):
- f, g = relabel(f, g)
- for fg in compose(f, g):
- yield delabel(fg)
-
-.. code:: ipython2
-
- for f in MC([dup], muls):
- print doc_from_stack_effect(*f)
-
-
-.. parsed-literal::
-
- (f1 -- f2)
- (i1 -- i2)
-
-
-.. code:: ipython2
-
-
-
- for f in MC([dup], [sum_]):
- print doc_from_stack_effect(*f)
-
-
-.. parsed-literal::
-
- ([n1* .1.] -- [n1* .1.] n1)
-
-
-.. code:: ipython2
-
-
-
- for f in MC([cons], [sum_]):
- print doc_from_stack_effect(*f)
-
-
-.. parsed-literal::
-
- (a1 [.1.] -- n1)
- (n1 [n1* .1.] -- n2)
-
-
-.. code:: ipython2
-
- sum_ = (((N[1], (Ns[1], S[1])), S[0]), (N[0], S[0]))
- print doc_from_stack_effect(*cons),
- print doc_from_stack_effect(*sum_),
-
- for f in MC([cons], [sum_]):
- print doc_from_stack_effect(*f)
-
-
-.. parsed-literal::
-
- (a1 [.1.] -- [a1 .1.]) ([n1 n1* .1.] -- n0) (n1 [n1* .1.] -- n2)
-
-
-.. code:: ipython2
-
- a = (A[4], (As[1], (A[3], S[1])))
- a
-
-
-
-
-.. parsed-literal::
-
- (a4, (a1*, (a3, s1)))
-
-
-
-.. code:: ipython2
-
- b = (A[1], (A[2], S[2]))
- b
-
-
-
-
-.. parsed-literal::
-
- (a1, (a2, s2))
-
-
-
-.. code:: ipython2
-
- for result in unify(b, a):
- print result
-
-
-.. parsed-literal::
-
- {a1: a4, s2: s1, a2: a3}
- {a1: a4, s2: (a1*, (a3, s1)), a2: a10003}
-
-
-.. code:: ipython2
-
- for result in unify(a, b):
- print result
-
-
-.. parsed-literal::
-
- {s2: s1, a2: a3, a4: a1}
- {s2: (a1*, (a3, s1)), a2: a10004, a4: a1}
-
-
-Part VII: Typing Combinators
-----------------------------
-
-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:
-
-::
-
- i (... [.1.] -- ... .1.)
-
-Or
-
-::
-
- i (... [A* .1.] -- ... A*)
-
-Consider the type of:
-
-::
-
- [cons] dip
-
-Obviously it would be:
-
-::
-
- (a1 [..1] a2 -- [a1 ..1] a2)
-
-``dip`` itself could have:
-
-::
-
- (a1 [..1] -- ... then what?
-
-Without any information about the contents of the quote we can’t say
-much about the result.
-
-Hybrid Inferencer/Interpreter
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-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.
-
-Joy Types for Functions
-^^^^^^^^^^^^^^^^^^^^^^^
-
-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.
-
-.. code:: ipython2
-
- class FunctionJoyType(AnyJoyType):
-
- def __init__(self, name, sec, number):
- self.name = name
- self.stack_effects = sec
- self.number = number
-
- def __add__(self, other):
- return self
- __radd__ = __add__
-
- def __repr__(self):
- return self.name
-
-Specialized for Simple Functions and Combinators
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-For non-combinator functions the stack effects list contains stack
-effect comments (represented by pairs of cons-lists as described above.)
-
-.. code:: ipython2
-
- class SymbolJoyType(FunctionJoyType):
- prefix = 'F'
-
-For combinators the list contains Python functions.
-
-.. code:: ipython2
-
- class CombinatorJoyType(FunctionJoyType):
-
- prefix = 'C'
-
- def __init__(self, name, sec, number, expect=None):
- super(CombinatorJoyType, self).__init__(name, sec, number)
- self.expect = expect
-
- def enter_guard(self, f):
- if self.expect is None:
- return f
- g = self.expect, self.expect
- new_f = list(compose(f, g, ()))
- assert len(new_f) == 1, repr(new_f)
- return new_f[0][1]
-
-For simple combinators that have only one effect (like ``dip``) you only
-need one function and it can be the combinator itself.
-
-.. code:: ipython2
-
- import joy.library
-
- dip = CombinatorJoyType('dip', [joy.library.dip], 23)
-
-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.
-
-.. code:: ipython2
-
- def branch_true(stack, expression, dictionary):
- (then, (else_, (flag, stack))) = stack
- return stack, concat(then, expression), dictionary
-
- def branch_false(stack, expression, dictionary):
- (then, (else_, (flag, stack))) = stack
- return stack, concat(else_, expression), dictionary
-
- branch = CombinatorJoyType('branch', [branch_true, branch_false], 100)
-
-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.
-
-``infer()``
-^^^^^^^^^^^
-
-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.
-
-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.
-
-.. code:: ipython2
-
- ID = S[0], S[0] # Identity function.
-
-
- def infer(*expression):
- return sorted(set(_infer(list_to_stack(expression))))
-
-
- def _infer(e, F=ID):
- _log_it(e, F)
- if not e:
- return [F]
-
- n, e = e
-
- if isinstance(n, SymbolJoyType):
- eFG = meta_compose([F], n.stack_effects, e)
- res = flatten(_infer(e, Fn) for e, Fn in eFG)
-
- elif isinstance(n, CombinatorJoyType):
- fi, fo = n.enter_guard(F)
- res = flatten(_interpret(f, fi, fo, e) for f in n.stack_effects)
-
- elif isinstance(n, Symbol):
- assert n not in FUNCTIONS, repr(n)
- func = joy.library._dictionary[n]
- res = _interpret(func, F[0], F[1], e)
-
- else:
- fi, fo = F
- res = _infer(e, (fi, (n, fo)))
-
- return res
-
-
- def _interpret(f, fi, fo, e):
- new_fo, ee, _ = f(fo, e, {})
- ee = update(FUNCTIONS, ee) # Fix Symbols.
- new_F = fi, new_fo
- return _infer(ee, new_F)
-
-
- def _log_it(e, F):
- _log.info(
- u'%3i %s ∘ %s',
- len(inspect_stack()),
- doc_from_stack_effect(*F),
- expression_to_string(e),
- )
-
-Work in Progress
-^^^^^^^^^^^^^^^^
-
-And that brings us to current Work-In-Progress. The mixed-mode
-inferencer/interpreter ``infer()`` function seems to work well. There
-are details I should document, and the rest of the code in the ``types``
-module (FIXME link to its docs here!) should be explained… There is
-cruft to convert the definitions in ``DEFS`` to the new
-``SymbolJoyType`` objects, and some combinators. Here is an example of
-output from the current code :
-
-.. code:: ipython2
-
- 1/0 # (Don't try to run this cell! It's not going to work. This is "read only" code heh..)
-
- logging.basicConfig(format='%(message)s', stream=sys.stdout, level=logging.INFO)
-
- globals().update(FUNCTIONS)
-
- h = infer((pred, s2), (mul, s3), (div, s4), (nullary, (bool, s5)), dipd, branch)
-
- print '-' * 40
-
- for fi, fo in h:
- print doc_from_stack_effect(fi, fo)
-
-
-::
-
-
- ---------------------------------------------------------------------------
-
- ZeroDivisionError Traceback (most recent call last)
-
- <ipython-input-1-9a9d60354c35> in <module>()
- ----> 1 1/0 # (Don't try to run this cell! It's not going to work. This is "read only" code heh..)
- 2
- 3 logging.basicConfig(format='%(message)s', stream=sys.stdout, level=logging.INFO)
- 4
- 5 globals().update(FUNCTIONS)
-
-
- ZeroDivisionError: integer division or modulo by zero
-
-
-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.)
-
-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.
-
-::
-
- 7 (--) ∘ [pred] [mul] [div] [nullary bool] dipd branch
- 8 (-- [pred ...2]) ∘ [mul] [div] [nullary bool] dipd branch
- 9 (-- [pred ...2] [mul ...3]) ∘ [div] [nullary bool] dipd branch
- 10 (-- [pred ...2] [mul ...3] [div ...4]) ∘ [nullary bool] dipd branch
- 11 (-- [pred ...2] [mul ...3] [div ...4] [nullary bool ...5]) ∘ dipd branch
- 15 (-- [pred ...5]) ∘ nullary bool [mul] [div] branch
- 19 (-- [pred ...2]) ∘ [stack] dinfrirst bool [mul] [div] branch
- 20 (-- [pred ...2] [stack ]) ∘ dinfrirst bool [mul] [div] branch
- 22 (-- [pred ...2] [stack ]) ∘ dip infra first bool [mul] [div] branch
- 26 (--) ∘ stack [pred] infra first bool [mul] [div] branch
- 29 (... -- ... [...]) ∘ [pred] infra first bool [mul] [div] branch
- 30 (... -- ... [...] [pred ...1]) ∘ infra first bool [mul] [div] branch
- 34 (--) ∘ pred s1 swaack first bool [mul] [div] branch
- 37 (n1 -- n2) ∘ [n1] swaack first bool [mul] [div] branch
- 38 (... n1 -- ... n2 [n1 ...]) ∘ swaack first bool [mul] [div] branch
- 41 (... n1 -- ... n1 [n2 ...]) ∘ first bool [mul] [div] branch
- 44 (n1 -- n1 n2) ∘ bool [mul] [div] branch
- 47 (n1 -- n1 b1) ∘ [mul] [div] branch
- 48 (n1 -- n1 b1 [mul ...1]) ∘ [div] branch
- 49 (n1 -- n1 b1 [mul ...1] [div ...2]) ∘ branch
- 53 (n1 -- n1) ∘ div
- 56 (f2 f1 -- f3) ∘
- 56 (i1 f1 -- f2) ∘
- 56 (f1 i1 -- f2) ∘
- 56 (i2 i1 -- f1) ∘
- 53 (n1 -- n1) ∘ mul
- 56 (f2 f1 -- f3) ∘
- 56 (i1 f1 -- f2) ∘
- 56 (f1 i1 -- f2) ∘
- 56 (i2 i1 -- i3) ∘
- ----------------------------------------
- (f2 f1 -- f3)
- (i1 f1 -- f2)
- (f1 i1 -- f2)
- (i2 i1 -- f1)
- (i2 i1 -- i3)
-
-Conclusion
-----------
-
-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
-
-Work remains to be done:
-
-- the rest of the library has to be covered
-- figure out how to deal with ``loop`` and ``genrec``, etc..
-- extend the types to check values (see the appendix)
-- other kinds of “higher order” type variables, OR, AND, etc..
-- maybe rewrite in Prolog for great good?
-- definitions
-
- - don’t permit composition of functions that don’t compose
- - auto-compile compilable functions
-
-- Compiling more than just the Yin functions.
-- getting better visibility (than Python debugger.)
-- DOOOOCS!!!! Lots of docs!
-
- - docstrings all around
- - 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.
-
-Appendix: Joy in the Logical Paradigm
--------------------------------------
-
-For *type checking* to work the type label classes have to be modified
-to let ``T >= t`` succeed, where e.g. ``T`` is ``IntJoyType`` and ``t``
-is ``int``. If you do that you can take advantage of the *logical
-relational* nature of the stack effect comments to “compute in reverse”
-as it were. There’s a working demo of this at the end of the ``types``
-module. But if you’re interested in all that you should just use Prolog!
-
-Anyhow, type *checking* is a few easy steps away.
-
-.. code:: ipython2
-
- def _ge(self, other):
- return (issubclass(other.__class__, self.__class__)
- or hasattr(self, 'accept')
- and isinstance(other, self.accept))
-
- AnyJoyType.__ge__ = _ge
- AnyJoyType.accept = tuple, int, float, long, str, unicode, bool, Symbol
- StackJoyType.accept = tuple