OSDN Git Service

Moving right along.
authorSimon Forman <sforman@hushmail.com>
Wed, 18 Jul 2018 00:12:27 +0000 (17:12 -0700)
committerSimon Forman <sforman@hushmail.com>
Wed, 18 Jul 2018 00:12:27 +0000 (17:12 -0700)
A little clunky but it seems to work so far.

joy/library.py
joy/utils/polytypes.py
joy/utils/types.py

index 449c48d..5e9bba3 100644 (file)
@@ -25,11 +25,12 @@ function.
 '''
 from inspect import getdoc
 from functools import wraps
+from itertools import count
 from inspect import getmembers, isfunction
 import operator, math
 
 from .parser import text_to_expression, Symbol
-from .utils.stack import list_to_stack, iter_stack, pick, concat
+from .utils.stack import expression_to_string, list_to_stack, iter_stack, pick, concat
 from .utils.brutal_hackery import rename_code_object
 
 from .utils import generated_library as genlib
@@ -38,16 +39,28 @@ from .utils.types import (
   ef,
   stack_effect,
   AnyJoyType,
+  AnyStarJoyType,
   BooleanJoyType,
   NumberJoyType,
+  NumberStarJoyType,
   StackJoyType,
+  StackStarJoyType,
   FloatJoyType,
   IntJoyType,
+  SymbolJoyType,
   TextJoyType,
   _functions,
+  FUNCTIONS,
+  infer,
+  JoyTypeError,
+  combinator_effect,
   )
   
   
+_SYM_NUMS = count().next
+_COMB_NUMS = count().next
+
+
 _R = range(10)
 A = a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 = map(AnyJoyType, _R)
 B = b0, b1, b2, b3, b4, b5, b6, b7, b8, b9 = map(BooleanJoyType, _R)
@@ -58,6 +71,12 @@ I = i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 = map(IntJoyType, _R)
 T = t0, t1, t2, t3, t4, t5, t6, t7, t8, t9 = map(TextJoyType, _R)
 
 
+_R = range(1, 11)
+As = map(AnyStarJoyType, _R)
+Ns = map(NumberStarJoyType, _R)
+Ss = map(StackStarJoyType, _R)
+
+
 sec0 = stack_effect(t1)()
 sec1 = stack_effect(s0, i1)(s1)
 sec2 = stack_effect(s0, i1)(a1)
@@ -67,7 +86,7 @@ sec_binary_logic = stack_effect(b1, b2)(b3)
 sec_binary_math = stack_effect(n1, n2)(n3)
 sec_unary_logic = stack_effect(a1)(b1)
 sec_unary_math = stack_effect(n1)(n2)
-
+sec_Ns_math = stack_effect((Ns[1], s1),)(n0)
 
 _dictionary = {}
 
@@ -350,6 +369,14 @@ class DefinitionWrapper(object):
     Add the definition to the dictionary.
     '''
     F = class_.parse_definition(definition)
+    try:
+      #print F._body
+      secs = infer(*F._body)
+    except JoyTypeError:
+      pass
+      print F.name, '==', expression_to_string(F.body), '   --failed to infer stack effect.'
+    else:
+      FUNCTIONS[F.name] = SymbolJoyType(F.name, secs, _SYM_NUMS())
     dictionary[F.name] = F
 
 
@@ -357,23 +384,11 @@ def _text_to_defs(text):
   return (line.strip() for line in text.splitlines() if '==' in line)
 
 
-
-##  eh = compose(dup, bool_)
-##  sqr = compose(dup, mul)
-##  of = compose(swap, at)
-
-
 #
 # Functions
 #
 
 
-# Load the auto-generated primitives into the dictionary.
-_functions.update(yin_functions())
-for name, primitive in getmembers(genlib, isfunction):
-  inscribe(SimpleFunctionWrapper(primitive))
-
-
 @inscribe
 @sec0
 @FunctionWrapper
@@ -528,6 +543,7 @@ def select(stack):
 
 
 @inscribe
+@sec_Ns_math
 @SimpleFunctionWrapper
 def max_(S):
   '''Given a list find the maximum.'''
@@ -536,6 +552,7 @@ def max_(S):
 
 
 @inscribe
+@sec_Ns_math
 @SimpleFunctionWrapper
 def min_(S):
   '''Given a list find the minimum.'''
@@ -544,6 +561,7 @@ def min_(S):
 
 
 @inscribe
+@sec_Ns_math
 @SimpleFunctionWrapper
 def sum_(S):
   '''Given a quoted sequence of numbers return the sum.
@@ -872,6 +890,7 @@ S_truthy = Symbol('truthy')
 
 
 @inscribe
+@combinator_effect(_COMB_NUMS(), s1)
 @FunctionWrapper
 def i(stack, expression, dictionary):
   '''
@@ -889,6 +908,7 @@ def i(stack, expression, dictionary):
 
 
 @inscribe
+@combinator_effect(_COMB_NUMS(), s1)
 @FunctionWrapper
 def x(stack, expression, dictionary):
   '''
@@ -906,6 +926,7 @@ def x(stack, expression, dictionary):
 
 
 @inscribe
+#@combinator_effect(_COMB_NUMS(), s7, s6)
 @FunctionWrapper
 def b(stack, expression, dictionary):
   '''
@@ -941,6 +962,7 @@ def dupdip(stack, expression, dictionary):
 
 
 @inscribe
+#@combinator_effect(_COMB_NUMS(), s7, s6)
 @FunctionWrapper
 def infra(stack, expression, dictionary):
   '''
@@ -1150,6 +1172,7 @@ def _cond(conditions, expression):
 
 
 @inscribe
+@combinator_effect(_COMB_NUMS(), a1, s1)
 @FunctionWrapper
 def dip(stack, expression, dictionary):
   '''
@@ -1437,7 +1460,31 @@ for F in (
 del F  # Otherwise Sphinx autodoc will pick it up.
 
 
+YIN_STACK_EFFECTS = yin_functions()
+
+# Load the auto-generated primitives into the dictionary.
+_functions.update(YIN_STACK_EFFECTS)
+# exec '''
+
+# eh = compose(dup, bool)
+# sqr = compose(dup, mul)
+# of = compose(swap, at)
+
+# ''' in dict(compose=compose), _functions
+
+FUNCTIONS.update(
+  (name, SymbolJoyType(name, [_functions[name]], _SYM_NUMS()))
+  for name in sorted(_functions)
+  )
+for name, primitive in getmembers(genlib, isfunction):
+  inscribe(SimpleFunctionWrapper(primitive))
+
+
 add_aliases(_dictionary, ALIASES)
+add_aliases(_functions, ALIASES)
+add_aliases(FUNCTIONS, ALIASES)
 
 
 DefinitionWrapper.add_definitions(definitions, _dictionary)
+
+#sec_Ns_math(_dictionary['product'])
index 4818ab6..9ce03b1 100644 (file)
@@ -10,12 +10,6 @@ an unbounded sequence of other types.
 
 '''
 import sys
-from inspect import stack as inspect_stack
-from itertools import chain, product
-from logging import getLogger
-
-_log = getLogger(__name__)
-
 import joy.library
 from joy.parser import Symbol, text_to_expression
 from joy.utils.stack import (
@@ -23,391 +17,22 @@ from joy.utils.stack import (
     expression_to_string,
     list_to_stack,
     )
-from joy.utils.types import (
-  AnyJoyType, A,
-  BooleanJoyType, B,
-  DEFS,
-  doc_from_stack_effect,
-  FloatJoyType, F,
-  JoyTypeError,
-  NumberJoyType, N,
-  StackJoyType, S,
-  _stacky,
-  _R,
-  relabel, delabel,
-  reify,
-  )
-
-
-# We no longer want FloatJoyType to accept IntJoyType.
-class IntJoyType(NumberJoyType): prefix = 'i'
-
-
-class KleeneStar(object):
-    u'''
-    A sequence of zero or more `AnyJoyType` variables would be:
-
-       A*
-
-    The `A*` works by splitting the universe into two alternate histories:
-
-       A* → ∅
-
-       A* → 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.
-    '''
-
-    kind = AnyJoyType
-
-    def __init__(self, number):
-        assert 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
-
-
-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
-
-
-class SymbolJoyType(FunctionJoyType):
-    '''
-    Represent non-combinator functions.
-
-    These type variables carry the stack effect comments and can
-    appear in expressions (as in quoted programs.)
-    '''
-    prefix = 'F'
-
-
-class CombinatorJoyType(FunctionJoyType):
-    '''
-    Represent combinators.
-    
-    These type variables carry Joy functions that implement the
-    behaviour of Joy combinators and they can appear in expressions.
-    For simple combinators the implementation functions can be the
-    combinators themselves.
-
-    These types can also specify a stack effect (input side only) to
-    guard against being used on invalid types.
-    '''
-
-    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]
-
-
-def _log_uni(U):
-  def inner(u, v, s=None):
-    _log.debug(
-      '%3i %s U %s   w/ %s',
-      len(inspect_stack()), u, v, s,
-      )
-    res = U(u, v, s)
-    _log.debug(
-      '%3i %s U %s   w/ %s => %s',
-      len(inspect_stack()), u, v, s, res,
-      )
-    return res
-  return inner
+# from joy.utils.types import (
+#   AnyJoyType, A,
+#   BooleanJoyType, B,
+#   DEFS,
+#   doc_from_stack_effect,
+#   FloatJoyType, F,
+#   JoyTypeError,
+#   NumberJoyType, N,
+#   StackJoyType, S,
+#   _stacky,
+#   _R,
+#   relabel, delabel,
+#   reify,
+#   )
 
 
-@_log_uni
-def unify(u, v, s=None):
-    '''
-    Return a tuple of substitution dicts representing unifiers for u and v.
-    '''
-    if s is None:
-        s = {}
-    elif s:
-        u = reify(s, u)
-        v = reify(s, v)
-
-    if u == v:
-        res = s,
-
-    elif isinstance(u, tuple) and isinstance(v, tuple):
-        if len(u) != 2 or len(v) != 2:
-            if _that_one_special_case(u, v):
-                return s,
-            raise ValueError(repr((u, v)))  # Bad input.
-            
-
-        (a, b), (c, d) = v, u
-        if isinstance(a, KleeneStar):
-            if isinstance(c, KleeneStar):
-                s = _lil_uni(a, c, s)  # Attempt to unify the two K-stars.
-                res = unify(d, b, s[0])
-
-            else:
-                # 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))
-                
-                res = s0 + s1
-                for sn in res:
-                    sn.update(s)
-
-        elif isinstance(c, KleeneStar):
-            res = unify(v, d) + unify(v, (c.another(), u))
-            for sn in res:
-              sn.update(s)
-
-        else:
-            res = tuple(flatten(unify(d, b, sn) for sn in unify(c, a, s)))
-    elif isinstance(v, tuple):
-        if not _stacky(u):
-            raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
-        s[u] = v
-        res = s,
-
-    elif isinstance(u, tuple):
-        if not _stacky(v):
-            raise JoyTypeError('Cannot unify %r and %r.' % (v, u))
-        s[v] = u
-        res = s,
-
-    else:
-        res = _lil_uni(u, v, s)
-
-    return res
-
-
-def _that_one_special_case(u, v):
-    '''
-    Handle e.g. ((), (n1*, s1)) when type-checking sum, product, etc...
-    '''
-    return (
-        u == ()
-        and len(v) == 2
-        and isinstance(v[0], KleeneStar)
-        and isinstance(v[1], StackJoyType)
-        )
-
-
-def _lil_uni(u, v, s):
-    if u >= v:
-        s[u] = v
-        return s,
-    if v >= u:
-        s[v] = u
-        return s,
-    raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
-
-
-def _compose(f, g, e):
-    (f_in, f_out), (g_in, g_out) = f, g
-    for s in unify(g_in, f_out):
-        yield reify(s, (e, (f_in, g_out)))
-
-
-def compose(f, g, e):
-    '''
-    Yield the stack effects of the composition of two stack effects.  An
-    expression is carried along and updated and yielded.
-    '''
-    f, g = relabel(f, g)
-    for fg in _compose(f, g, e):
-        yield delabel(fg)
-
-
-def _meta_compose(F, G, e):
-    for f, g in product(F, G):
-        try:
-            for result in compose(f, g, e): yield result
-        except JoyTypeError:
-            pass
-
-
-def meta_compose(F, G, e):
-    '''
-    Yield the stack effects of the composition of two lists of stack
-    effects.  An expression is carried along and updated and yielded.
-    '''
-    res = sorted(set(_meta_compose(F, G, e)))
-    if not res:
-        raise JoyTypeError('Cannot unify %r and %r.' % (F, G))
-    return res
-
-
-def flatten(g):
-  return list(chain.from_iterable(g))
-
-
-ID = S[0], S[0]  # Identity function.
-
-
-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 = reify(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),
-        )
-
-
-def infer(*expression):
-    '''
-    Return a list of stack effects for a Joy expression.
-
-    For example::
-
-        h = infer(pop, swap, rolldown, rest, rest, cons, cons)
-        for fi, fo in h:
-            print doc_from_stack_effect(fi, fo)
-
-    Prints::
-
-        ([a4 a5 ...1] a3 a2 a1 -- [a2 a3 ...1])
-
-    '''
-    return sorted(set(_infer(list_to_stack(expression))))
-
-
-def infer_string(string):
-    e = reify(FUNCTIONS, text_to_expression(string))  # Fix Symbols.
-    return sorted(set(_infer(e)))
-
-
-def infer_expression(expression):
-    e = reify(FUNCTIONS, expression)  # Fix Symbols.
-    return sorted(set(_infer(e)))
-
-
-def type_check(name, stack):
-    '''
-    Trinary predicate.  True if named function type-checks, False if it
-    fails, None if it's indeterminate (because I haven't entered it into
-    the FUNCTIONS dict yet.)
-    '''
-    try:
-        func = FUNCTIONS[name]
-    except KeyError:
-        return # None, indicating unknown
-
-    for fi, fo in infer(func):
-        try:
-          U = unify(fi, stack)
-        except (JoyTypeError, ValueError), e:
-            #print >> sys.stderr, name, e, stack
-            continue
-        #print U
-        return True
-    return False
-
-
-a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 = A
-b0, b1, b2, b3, b4, b5, b6, b7, b8, b9 = B
-n0, n1, n2, n3, n4, n5, n6, n7, n8, n9 = N
-f0, f1, f2, f3, f4, f5, f6, f7, f8, f9 = F
-i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 = I = map(IntJoyType, _R)
-s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 = S
-
-_R = range(1, 11)
-As = map(AnyStarJoyType, _R)
-Ns = map(NumberStarJoyType, _R)
-Ss = map(StackStarJoyType, _R)
-
-
-FUNCTIONS = {
-    name: SymbolJoyType(name, [DEFS[name]], i)
-    for i, name in enumerate(sorted(DEFS))
-    }
 '''Docstring for functions in Sphinx?'''
 
 
@@ -452,6 +77,7 @@ FUNCTIONS.update({
         ))
     })
 
+
 def branch_true(stack, expression, dictionary):
   (then, (else_, (flag, stack))) = stack
   return stack, CONCAT(then, expression), dictionary
index 224003a..fe4c6a8 100644 (file)
@@ -1,7 +1,13 @@
+# -*- coding: utf_8
+from logging import getLogger
+
+_log = getLogger(__name__)
+
 from collections import Counter
-from itertools import imap
-from joy.utils.stack import concat
-from joy.parser import Symbol
+from itertools import imap, chain, product
+from inspect import stack as inspect_stack
+from joy.utils.stack import concat, expression_to_string, list_to_stack
+from joy.parser import Symbol, text_to_expression
 
 
 class AnyJoyType(object):
@@ -49,7 +55,7 @@ class BooleanJoyType(AnyJoyType):
 
 
 class NumberJoyType(AnyJoyType):
-  accept = int, float, long, complex
+  accept = bool, int, float, long, complex
   prefix = 'n'
 
 
@@ -78,6 +84,118 @@ class StackJoyType(AnyJoyType):
     return False
 
 
+class KleeneStar(object):
+    u'''
+    A sequence of zero or more `AnyJoyType` variables would be:
+
+       A*
+
+    The `A*` works by splitting the universe into two alternate histories:
+
+       A* → ∅
+
+       A* → 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.
+    '''
+
+    kind = AnyJoyType
+
+    def __init__(self, number):
+        assert 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
+class TextStarJoyType(KleeneStar): kind = TextJoyType
+
+
+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
+
+
+class SymbolJoyType(FunctionJoyType):
+    '''
+    Represent non-combinator functions.
+
+    These type variables carry the stack effect comments and can
+    appear in expressions (as in quoted programs.)
+    '''
+    prefix = 'F'
+
+
+class CombinatorJoyType(FunctionJoyType):
+    '''
+    Represent combinators.
+    
+    These type variables carry Joy functions that implement the
+    behaviour of Joy combinators and they can appear in expressions.
+    For simple combinators the implementation functions can be the
+    combinators themselves.
+
+    These types can also specify a stack effect (input side only) to
+    guard against being used on invalid types.
+    '''
+
+    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(poly_compose(f, g, ()))
+        assert len(new_f) == 1, repr(new_f)
+        return new_f[0][1]
+
+
 class JoyTypeError(Exception): pass
 
 
@@ -135,7 +253,7 @@ def delabel(f, seen=None, c=None):
   return tuple(delabel(inner, seen, c) for inner in f)
 
 
-def unify(u, v, s=None):
+def uni_unify(u, v, s=None):
   '''
   Return a substitution dict representing a unifier for u and v.
   '''
@@ -157,7 +275,7 @@ def unify(u, v, s=None):
     if len(u) != len(v) != 2:
       raise ValueError(repr((u, v)))  # Bad input.
     (a, b), (c, d) = u, v
-    s = unify(b, d, unify(a, c, s))
+    s = uni_unify(b, d, uni_unify(a, c, s))
 
   elif isinstance(v, tuple):
     if not _stacky(u):
@@ -175,6 +293,112 @@ def unify(u, v, s=None):
   return s
 
 
+def _log_uni(U):
+  def inner(u, v, s=None):
+    _log.debug(
+      '%3i %s U %s   w/ %s',
+      len(inspect_stack()), u, v, s,
+      )
+    res = U(u, v, s)
+    _log.debug(
+      '%3i %s U %s   w/ %s => %s',
+      len(inspect_stack()), u, v, s, res,
+      )
+    return res
+  return inner
+
+
+@_log_uni
+def unify(u, v, s=None):
+    '''
+    Return a tuple of substitution dicts representing unifiers for u and v.
+    '''
+    if s is None:
+        s = {}
+    elif s:
+        u = reify(s, u)
+        v = reify(s, v)
+
+    if u == v:
+        res = s,
+
+    elif isinstance(u, tuple) and isinstance(v, tuple):
+        if len(u) != 2 or len(v) != 2:
+            if _that_one_special_case(u, v):
+                return s,
+            raise ValueError(repr((u, v)))  # Bad input.
+            
+
+        (a, b), (c, d) = v, u
+        if isinstance(a, KleeneStar):
+            if isinstance(c, KleeneStar):
+                s = _lil_uni(a, c, s)  # Attempt to unify the two K-stars.
+                res = unify(d, b, s[0])
+
+            else:
+                # 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))
+                
+                res = s0 + s1
+                for sn in res:
+                    sn.update(s)
+
+        elif isinstance(c, KleeneStar):
+            res = unify(v, d) + unify(v, (c.another(), u))
+            for sn in res:
+              sn.update(s)
+
+        else:
+            res = tuple(flatten(unify(d, b, sn) for sn in unify(c, a, s)))
+    elif isinstance(v, tuple):
+        if not _stacky(u):
+            raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
+        s[u] = v
+        res = s,
+
+    elif isinstance(u, tuple):
+        if not _stacky(v):
+            raise JoyTypeError('Cannot unify %r and %r.' % (v, u))
+        s[v] = u
+        res = s,
+
+    else:
+        res = _lil_uni(u, v, s)
+
+    return res
+
+
+def _that_one_special_case(u, v):
+    '''
+    Handle e.g. ((), (n1*, s1)) when type-checking sum, product, etc...
+    '''
+    return (
+        u == ()
+        and len(v) == 2
+        and isinstance(v[0], KleeneStar)
+        and isinstance(v[1], StackJoyType)
+        )
+
+
+def flatten(g):
+  return list(chain.from_iterable(g))
+
+
+def _lil_uni(u, v, s):
+    if u >= v:
+        s[u] = v
+        return s,
+    if v >= u:
+        s[v] = u
+        return s,
+    raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
+
+
 def _stacky(thing):
   return thing.__class__ in {AnyJoyType, StackJoyType}
 
@@ -185,7 +409,7 @@ def _compose(f, g):
   '''
   # Relabel, unify, update, delabel.
   (f_in, f_out), (g_in, g_out) = relabel(f, g)
-  fg = reify(unify(g_in, f_out), (f_in, g_out))
+  fg = reify(uni_unify(g_in, f_out), (f_in, g_out))
   return delabel(fg)
 
 
@@ -275,7 +499,144 @@ def compile_(name, f, doc=None):
   return %s''' % (name, doc, i, o)
 
 
-_functions = {}
+def _poly_compose(f, g, e):
+    (f_in, f_out), (g_in, g_out) = f, g
+    for s in unify(g_in, f_out):
+        yield reify(s, (e, (f_in, g_out)))
+
+
+def poly_compose(f, g, e):
+    '''
+    Yield the stack effects of the composition of two stack effects.  An
+    expression is carried along and updated and yielded.
+    '''
+    f, g = relabel(f, g)
+    for fg in _poly_compose(f, g, e):
+        yield delabel(fg)
+
+
+def _meta_compose(F, G, e):
+    for f, g in product(F, G):
+        try:
+            for result in poly_compose(f, g, e): yield result
+        except JoyTypeError:
+            pass
+
+
+def meta_compose(F, G, e):
+    '''
+    Yield the stack effects of the composition of two lists of stack
+    effects.  An expression is carried along and updated and yielded.
+    '''
+    res = sorted(set(_meta_compose(F, G, e)))
+    if not res:
+        raise JoyTypeError('Cannot unify %r and %r.' % (F, G))
+    return res
+
+
+_S0 = StackJoyType(0)
+ID = _S0, _S0  # Identity function.
+
+
+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):
+        if n in FUNCTIONS:
+          res =_infer((FUNCTIONS[n], e), F)
+        else:
+          raise JoyTypeError
+        #   print 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 = reify(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),
+        )
+
+
+def infer(*expression):
+    '''
+    Return a list of stack effects for a Joy expression.
+
+    For example::
+
+        h = infer(pop, swap, rolldown, rest, rest, cons, cons)
+        for fi, fo in h:
+            print doc_from_stack_effect(fi, fo)
+
+    Prints::
+
+        ([a4 a5 ...1] a3 a2 a1 -- [a2 a3 ...1])
+
+    '''
+    return sorted(set(_infer(list_to_stack(expression))))
+
+
+def infer_string(string):
+    e = reify(FUNCTIONS, text_to_expression(string))  # Fix Symbols.
+    return sorted(set(_infer(e)))
+
+
+def infer_expression(expression):
+    e = reify(FUNCTIONS, expression)  # Fix Symbols.
+    return sorted(set(_infer(e)))
+
+
+def type_check(name, stack):
+    '''
+    Trinary predicate.  True if named function type-checks, False if it
+    fails, None if it's indeterminate (because I haven't entered it into
+    the FUNCTIONS dict yet.)
+    '''
+    try:
+        func = FUNCTIONS[name]
+    except KeyError:
+        return # None, indicating unknown
+
+    for fi, fo in infer(func):
+        try:
+          U = unify(fi, stack)
+        except (JoyTypeError, ValueError), e:
+            #print >> sys.stderr, name, e, stack
+            continue
+        #print U
+        return True
+    return False
+
+
+FUNCTIONS = {}  # Polytypes (lists of stack effects.)
+_functions = {}  # plain ol' stack effects.
 
 
 def __(*seq):
@@ -303,6 +664,14 @@ def ef(*inputs):
   return _ef
 
 
+def combinator_effect(number, *expect):
+    def _combinator_effect(c):
+        C = FUNCTIONS[c.name] = CombinatorJoyType(c.name, [c], number)
+        if expect: C.expect = __(*expect)
+        return c
+    return _combinator_effect
+
+
 def show(DEFS):
   for name, stack_effect_comment in sorted(DEFS.iteritems()):
     t = ' *'[compilable(stack_effect_comment)]