From 3b0b7659b378f8722f4722d522b79a9d1d1ffb0d Mon Sep 17 00:00:00 2001 From: Simon Forman Date: Fri, 24 Aug 2018 16:48:15 -0700 Subject: [PATCH] A bunch of docs. Things have kind of run away with me. I've been working in Prolog for the last month or so. I'm not yet sure how to bring it together with the Python code. --- docs/Compiling_Joy.html | 13543 +++++++++++++++++++ docs/Compiling_Joy.ipynb | 1310 ++ docs/Compiling_Joy.md | 724 + docs/Compiling_Joy.rst | 788 ++ docs/fun_with_scan.html | 12319 +++++++++++++++++ docs/fun_with_scan.ipynb | 441 + docs/fun_with_scan.md | 234 + docs/fun_with_scan.rst | 334 + docs/notebook_preamble.pyc | Bin 0 -> 1158 bytes docs/with_sympy.html | 14034 ++++++++++++++++++++ docs/with_sympy.ipynb | 1873 +++ docs/with_sympy.md | 1261 ++ docs/with_sympy.rst | 1313 ++ docs/with_sympy_files/with_sympy_11_0.png | Bin 0 -> 1201 bytes docs/with_sympy_files/with_sympy_16_0.png | Bin 0 -> 2003 bytes docs/with_sympy_files/with_sympy_17_0.png | Bin 0 -> 2032 bytes docs/with_sympy_files/with_sympy_38_0.png | Bin 0 -> 403 bytes docs/with_sympy_files/with_sympy_44_1.png | Bin 0 -> 403 bytes docs/with_sympy_files/with_sympy_9_0.png | Bin 0 -> 956 bytes joy/utils/infinite_stack.py | 30 + 20 files changed, 48204 insertions(+) create mode 100644 docs/Compiling_Joy.html create mode 100644 docs/Compiling_Joy.ipynb create mode 100644 docs/Compiling_Joy.md create mode 100644 docs/Compiling_Joy.rst create mode 100644 docs/fun_with_scan.html create mode 100644 docs/fun_with_scan.ipynb create mode 100644 docs/fun_with_scan.md create mode 100644 docs/fun_with_scan.rst create mode 100644 docs/notebook_preamble.pyc create mode 100644 docs/with_sympy.html create mode 100644 docs/with_sympy.ipynb create mode 100644 docs/with_sympy.md create mode 100644 docs/with_sympy.rst create mode 100644 docs/with_sympy_files/with_sympy_11_0.png create mode 100644 docs/with_sympy_files/with_sympy_16_0.png create mode 100644 docs/with_sympy_files/with_sympy_17_0.png create mode 100644 docs/with_sympy_files/with_sympy_38_0.png create mode 100644 docs/with_sympy_files/with_sympy_44_1.png create mode 100644 docs/with_sympy_files/with_sympy_9_0.png create mode 100644 joy/utils/infinite_stack.py diff --git a/docs/Compiling_Joy.html b/docs/Compiling_Joy.html new file mode 100644 index 0000000..961e001 --- /dev/null +++ b/docs/Compiling_Joy.html @@ -0,0 +1,13543 @@ + + + +Compiling_Joy + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
In [1]:
+
+
+
from notebook_preamble import D, J, V, define
+
+ +
+
+
+ +
+
+
+
+
+

Compiling Joy

Given a Joy program like:

+ +
sqr == dup mul
+ +
+
+
+
+
+
In [2]:
+
+
+
V('23 sqr')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
      . 23 sqr
+   23 . sqr
+   23 . dup mul
+23 23 . mul
+  529 . 
+
+
+
+ +
+
+ +
+
+
+
+
+

How would we go about compiling this code (to Python for now)?

+

Naive Call Chaining

The simplest thing would be to compose the functions from the library:

+ +
+
+
+
+
+
In [3]:
+
+
+
dup, mul = D['dup'], D['mul']
+
+ +
+
+
+ +
+
+
+
In [4]:
+
+
+
def sqr(stack, expression, dictionary):
+    return mul(*dup(stack, expression, dictionary))
+
+ +
+
+
+ +
+
+
+
In [5]:
+
+
+
old_sqr = D['sqr']
+D['sqr'] = sqr
+
+ +
+
+
+ +
+
+
+
In [6]:
+
+
+
V('23 sqr')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
    . 23 sqr
+ 23 . sqr
+529 . 
+
+
+
+ +
+
+ +
+
+
+
+
+

It's simple to write a function to emit this kind of crude "compiled" code.

+ +
+
+
+
+
+
In [7]:
+
+
+
def compile_joy(name, expression):
+    term, expression = expression
+    code = term +'(stack, expression, dictionary)'
+    format_ = '%s(*%s)'
+    while expression:
+        term, expression = expression
+        code = format_ % (term, code)
+    return '''\
+def %s(stack, expression, dictionary):
+    return %s
+''' % (name, code)
+
+
+def compile_joy_definition(defi):
+    return compile_joy(defi.name, defi.body)
+
+ +
+
+
+ +
+
+
+
In [8]:
+
+
+
print compile_joy_definition(old_sqr)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
def sqr(stack, expression, dictionary):
+    return mul(*dup(stack, expression, dictionary))
+
+
+
+
+ +
+
+ +
+
+
+
+
+

But what about literals?

+ +
quoted == [unit] dip
+ +
+
+
+
+
+
In [9]:
+
+
+
unit, dip = D['unit'], D['dip']
+
+ +
+
+
+ +
+
+
+
In [10]:
+
+
+
# print compile_joy_definition(D['quoted'])
+# raises
+# TypeError: can only concatenate tuple (not "str") to tuple
+
+ +
+
+
+ +
+
+
+
+
+

For a program like foo == bar baz 23 99 baq lerp barp we would want something like:

+ +
+
+
+
+
+
In [11]:
+
+
+
def foo(stack, expression, dictionary):
+    stack, expression, dictionary = baz(*bar(stack, expression, dictionary))
+    return barp(*lerp(*baq((99, (23, stack)), expression, dictionary)))
+
+ +
+
+
+ +
+
+
+
+
+

You have to have a little discontinuity when going from a symbol to a literal, because you have to pick out the stack from the arguments to push the literal(s) onto it before you continue chaining function calls.

+ +
+
+
+
+
+
+
+

Compiling Yin Functions

Call-chaining results in code that does too much work. For functions that operate on stacks and only rearrange values, what I like to call "Yin Functions", we can do better.

+

We can infer the stack effects of these functions (or "expressions" or "programs") automatically, and the stack effects completely define the semantics of the functions, so we can directly write out a two-line Python function for them. This is already implemented in the joy.utils.types.compile_() function.

+ +
+
+
+
+
+
In [12]:
+
+
+
from joy.utils.types import compile_, doc_from_stack_effect, infer_string
+from joy.library import SimpleFunctionWrapper
+
+ +
+
+
+ +
+
+
+
In [13]:
+
+
+
stack_effects = infer_string('tuck over dup')
+
+ +
+
+
+ +
+
+
+
+
+

Yin functions have only a single stack effect, they do not branch or loop.

+ +
+
+
+
+
+
In [14]:
+
+
+
for fi, fo in stack_effects:
+    print doc_from_stack_effect(fi, fo)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
(a2 a1 -- a1 a2 a1 a2 a2)
+
+
+
+ +
+
+ +
+
+
+
In [15]:
+
+
+
source = compile_('foo', stack_effects[0])
+
+ +
+
+
+ +
+
+
+
+
+

All Yin functions can be described in Python as a tuple-unpacking (or "-destructuring") of the stack datastructure followed by building up the new stack structure.

+ +
+
+
+
+
+
In [16]:
+
+
+
print source
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
def foo(stack):
+  """
+  ::
+
+  (a2 a1 -- a1 a2 a1 a2 a2)
+
+  """
+  (a1, (a2, s1)) = stack
+  return (a2, (a2, (a1, (a2, (a1, s1)))))
+
+
+
+ +
+
+ +
+
+
+
In [17]:
+
+
+
exec compile(source, '__main__', 'single')
+
+D['foo'] = SimpleFunctionWrapper(foo)
+
+ +
+
+
+ +
+
+
+
In [18]:
+
+
+
V('23 18 foo')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
               . 23 18 foo
+            23 . 18 foo
+         23 18 . foo
+18 23 18 23 23 . 
+
+
+
+ +
+
+ +
+
+
+
+
+

Compiling from Stack Effects

There are times when you're deriving a Joy program when you have a stack effect for a Yin function and you need to define it. For example, in the Ordered Binary Trees notebook there is a point where we must derive a function Ee:

+ +
   [key old_value left right] new_value key [Tree-add] Ee
+------------------------------------------------------------
+   [key new_value left right]
+
+
+

While it is not hard to come up with this function manually, there is no necessity. This function can be defined (in Python) directly from its stack effect:

+ +
   [a b c d] e a [f] Ee
+--------------------------
+   [a e c d]
+
+
+

(I haven't yet implemented a simple interface for this yet. What follow is an exploration of how to do it.)

+ +
+
+
+
+
+
In [19]:
+
+
+
from joy.parser import text_to_expression
+
+ +
+
+
+ +
+
+
+
In [20]:
+
+
+
Ein = '[a b c d] e a [f]'  # The terms should be reversed here but I don't realize that until later.
+Eout = '[a e c d]'
+E = '[%s] [%s]' % (Ein, Eout)
+
+print E
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[[a b c d] e a [f]] [[a e c d]]
+
+
+
+ +
+
+ +
+
+
+
In [21]:
+
+
+
(fi, (fo, _)) = text_to_expression(E)
+
+ +
+
+
+ +
+
+
+
In [22]:
+
+
+
fi, fo
+
+ +
+
+
+ +
+
+ + +
+ +
Out[22]:
+ + + + +
+
(((a, (b, (c, (d, ())))), (e, (a, ((f, ()), ())))),
+ ((a, (e, (c, (d, ())))), ()))
+
+ +
+ +
+
+ +
+
+
+
In [23]:
+
+
+
Ein = '[a1 a2 a3 a4] a5 a6 a7'
+Eout = '[a1 a5 a3 a4]'
+E = '[%s] [%s]' % (Ein, Eout)
+
+print E
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[[a1 a2 a3 a4] a5 a6 a7] [[a1 a5 a3 a4]]
+
+
+
+ +
+
+ +
+
+
+
In [24]:
+
+
+
(fi, (fo, _)) = text_to_expression(E)
+
+ +
+
+
+ +
+
+
+
In [25]:
+
+
+
fi, fo
+
+ +
+
+
+ +
+
+ + +
+ +
Out[25]:
+ + + + +
+
(((a1, (a2, (a3, (a4, ())))), (a5, (a6, (a7, ())))),
+ ((a1, (a5, (a3, (a4, ())))), ()))
+
+ +
+ +
+
+ +
+
+
+
In [26]:
+
+
+
def type_vars():
+    from joy.library import a1, a2, a3, a4, a5, a6, a7, s0, s1
+    return locals()
+
+tv = type_vars()
+tv
+
+ +
+
+
+ +
+
+ + +
+ +
Out[26]:
+ + + + +
+
{'a1': a1,
+ 'a2': a2,
+ 'a3': a3,
+ 'a4': a4,
+ 'a5': a5,
+ 'a6': a6,
+ 'a7': a7,
+ 's0': s0,
+ 's1': s1}
+
+ +
+ +
+
+ +
+
+
+
In [27]:
+
+
+
from joy.utils.types import reify
+
+ +
+
+
+ +
+
+
+
In [28]:
+
+
+
stack_effect = reify(tv, (fi, fo))
+print doc_from_stack_effect(*stack_effect)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
(... a7 a6 a5 [a1 a2 a3 a4 ] -- ... [a1 a5 a3 a4 ])
+
+
+
+ +
+
+ +
+
+
+
In [29]:
+
+
+
print stack_effect
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
(((a1, (a2, (a3, (a4, ())))), (a5, (a6, (a7, ())))), ((a1, (a5, (a3, (a4, ())))), ()))
+
+
+
+ +
+
+ +
+
+
+
+
+

Almost, but what we really want is something like this:

+ +
+
+
+
+
+
In [30]:
+
+
+
stack_effect = eval('(((a1, (a2, (a3, (a4, s1)))), (a5, (a6, (a7, s0)))), ((a1, (a5, (a3, (a4, s1)))), s0))', tv)
+
+ +
+
+
+ +
+
+
+
+
+

Note the change of () to JoyStackType type variables.

+ +
+
+
+
+
+
In [31]:
+
+
+
print doc_from_stack_effect(*stack_effect)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
(a7 a6 a5 [a1 a2 a3 a4 ...1] -- [a1 a5 a3 a4 ...1])
+
+
+
+ +
+
+ +
+
+
+
+
+

Now we can omit a3 and a4 if we like:

+ +
+
+
+
+
+
In [32]:
+
+
+
stack_effect = eval('(((a1, (a2, s1)), (a5, (a6, (a7, s0)))), ((a1, (a5, s1)), s0))', tv)
+
+ +
+
+
+ +
+
+
+
+
+

The right and left parts of the ordered binary tree node are subsumed in the tail of the node's stack/list.

+ +
+
+
+
+
+
In [33]:
+
+
+
print doc_from_stack_effect(*stack_effect)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
(a7 a6 a5 [a1 a2 ...1] -- [a1 a5 ...1])
+
+
+
+ +
+
+ +
+
+
+
In [34]:
+
+
+
source = compile_('Ee', stack_effect)
+print source
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
def Ee(stack):
+  """
+  ::
+
+  (a7 a6 a5 [a1 a2 ...1] -- [a1 a5 ...1])
+
+  """
+  ((a1, (a2, s1)), (a5, (a6, (a7, s0)))) = stack
+  return ((a1, (a5, s1)), s0)
+
+
+
+ +
+
+ +
+
+
+
+
+

Oops! The input stack is backwards...

+ +
+
+
+
+
+
In [35]:
+
+
+
stack_effect = eval('((a7, (a6, (a5, ((a1, (a2, s1)), s0)))), ((a1, (a5, s1)), s0))', tv)
+
+ +
+
+
+ +
+
+
+
In [36]:
+
+
+
print doc_from_stack_effect(*stack_effect)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
([a1 a2 ...1] a5 a6 a7 -- [a1 a5 ...1])
+
+
+
+ +
+
+ +
+
+
+
In [37]:
+
+
+
source = compile_('Ee', stack_effect)
+print source
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
def Ee(stack):
+  """
+  ::
+
+  ([a1 a2 ...1] a5 a6 a7 -- [a1 a5 ...1])
+
+  """
+  (a7, (a6, (a5, ((a1, (a2, s1)), s0)))) = stack
+  return ((a1, (a5, s1)), s0)
+
+
+
+ +
+
+ +
+
+
+
+
+

Compare:

+ +
   [key old_value left right] new_value key [Tree-add] Ee
+------------------------------------------------------------
+   [key new_value left right]
+ +
+
+
+
+
+
In [38]:
+
+
+
eval(compile(source, '__main__', 'single'))
+D['Ee'] = SimpleFunctionWrapper(Ee)
+
+ +
+
+
+ +
+
+
+
In [39]:
+
+
+
V('[a b c d] 1 2 [f] Ee')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
                  . [a b c d] 1 2 [f] Ee
+        [a b c d] . 1 2 [f] Ee
+      [a b c d] 1 . 2 [f] Ee
+    [a b c d] 1 2 . [f] Ee
+[a b c d] 1 2 [f] . Ee
+        [a 1 c d] . 
+
+
+
+ +
+
+ +
+
+
+
+
+

Working with Yang Functions

Consider the compiled code of dup:

+ +
+
+
+
+
+
In [40]:
+
+
+
def dup(stack):
+    (a1, s23) = stack
+    return (a1, (a1, s23))
+
+ +
+
+
+ +
+
+
+
+
+

To compile sqr == dup mul we can compute the stack effect:

+ +
+
+
+
+
+
In [41]:
+
+
+
stack_effects = infer_string('dup mul')
+for fi, fo in stack_effects:
+    print doc_from_stack_effect(fi, fo)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
(n1 -- n2)
+
+
+
+ +
+
+ +
+
+
+
+
+

Then we would want something like this:

+ +
+
+
+
+
+
In [42]:
+
+
+
def sqr(stack):
+    (n1, s23) = stack
+    n2 = mul(n1, n1)
+    return (n2, s23)
+
+ +
+
+
+ +
+
+
+
+
+

How about...

+ +
+
+
+
+
+
In [43]:
+
+
+
stack_effects = infer_string('mul mul sub')
+for fi, fo in stack_effects:
+    print doc_from_stack_effect(fi, fo)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
(n4 n3 n2 n1 -- n5)
+
+
+
+ +
+
+ +
+
+
+
In [44]:
+
+
+
def foo(stack):
+    (n1, (n2, (n3, (n4, s23)))) = stack
+    n5 = mul(n1, n2)
+    n6 = mul(n5, n3)
+    n7 = sub(n6, n4)
+    return (n7, s23)
+
+
+# or
+
+def foo(stack):
+    (n1, (n2, (n3, (n4, s23)))) = stack
+    n5 = sub(mul(mul(n1, n2), n3), n4)
+    return (n5, s23)
+
+ +
+
+
+ +
+
+
+
In [45]:
+
+
+
stack_effects = infer_string('tuck')
+for fi, fo in stack_effects:
+    print doc_from_stack_effect(fi, fo)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
(a2 a1 -- a1 a2 a1)
+
+
+
+ +
+
+ +
+
+
+
+
+

Compiling Yin~Yang Functions

+
+
+
+
+
+
+
+

First, we need a source of Python identifiers. I'm going to reuse Symbol class for this.

+ +
+
+
+
+
+
In [46]:
+
+
+
from joy.parser import Symbol
+
+ +
+
+
+ +
+
+
+
In [47]:
+
+
+
def _names():
+    n = 0
+    while True:
+        yield Symbol('a' + str(n))
+        n += 1
+
+names = _names().next
+
+ +
+
+
+ +
+
+
+
+
+

Now we need an object that represents a Yang function that accepts two args and return one result (we'll implement other kinds a little later.)

+ +
+
+
+
+
+
In [80]:
+
+
+
class Foo(object):
+
+    def __init__(self, name):
+        self.name = name
+
+    def __call__(self, stack, expression, code):
+        in1, (in0, stack) = stack
+        out = names()
+        code.append(('call', out, self.name, (in0, in1)))
+        return (out, stack), expression, code
+
+ +
+
+
+ +
+
+
+
+
+

A crude "interpreter" that translates expressions of args and Yin and Yang functions into a kind of simple dataflow graph.

+ +
+
+
+
+
+
In [64]:
+
+
+
def I(stack, expression, code):
+    while expression:
+        term, expression = expression
+        if callable(term):
+            stack, expression, _ = term(stack, expression, code)
+        else:
+            stack = term, stack
+            code.append(('pop', term))
+
+    s = []
+    while stack:
+        term, stack = stack
+        s.insert(0, term)
+    if s:
+        code.append(('push',) + tuple(s))
+    return code
+
+ +
+
+
+ +
+
+
+
+
+

Something to convert the graph into Python code.

+ +
+
+
+
+
+
In [50]:
+
+
+
strtup = lambda a, b: '(%s, %s)' % (b, a)
+strstk = lambda rest: reduce(strtup, rest, 'stack')
+
+
+def code_gen(code):
+    coalesce_pops(code)
+    lines = []
+    for t in code:
+        tag, rest = t[0], t[1:]
+
+        if tag == 'pop':
+            lines.append(strstk(rest) + ' = stack')
+
+        elif tag == 'push':
+            lines.append('stack = ' + strstk(rest))
+
+        elif tag == 'call':
+            #out, name, in_ = rest
+            lines.append('%s = %s%s' % rest)
+
+        else:
+            raise ValueError(tag)
+
+    return '\n'.join('    ' + line for line in lines)
+
+
+def coalesce_pops(code):
+    index = [i for i, t in enumerate(code) if t[0] == 'pop']
+    for start, end in yield_groups(index):
+        code[start:end] = \
+            [tuple(['pop'] + [t for _, t in code[start:end][::-1]])]
+
+
+def yield_groups(index):
+    '''
+    Yield slice indices for each group of contiguous ints in the
+    index list.
+    '''
+    k = 0
+    for i, (a, b) in enumerate(zip(index, index[1:])):
+        if b - a > 1:
+            if k != i:
+                yield index[k], index[i] + 1
+            k = i + 1
+    if k < len(index):
+        yield index[k], index[-1] + 1
+
+
+def compile_yinyang(name, expression):
+    return '''\
+def %s(stack):
+%s
+    return stack
+''' % (name, code_gen(I((), expression, [])))
+
+ +
+
+
+ +
+
+
+
+
+

A few functions to try it with...

+ +
+
+
+
+
+
In [69]:
+
+
+
mul = Foo('mul')
+sub = Foo('sub')
+
+ +
+
+
+ +
+
+
+
In [74]:
+
+
+
def import_yin():
+    from joy.utils.generated_library import *
+    return locals()
+
+yin_dict = {name: SimpleFunctionWrapper(func) for name, func in import_yin().iteritems()}
+
+yin_dict
+
+dup = yin_dict['dup']
+
+#def dup(stack, expression, code):
+#    n, stack = stack
+#    return (n, (n, stack)), expression
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
<ipython-input-74-a6ea700b09d9>:1: SyntaxWarning: import * only allowed at module level
+  def import_yin():
+
+
+
+ +
+
+ +
+
+
+
+
+

... and there we are.

+ +
+
+
+
+
+
In [75]:
+
+
+
print compile_yinyang('mul_', (names(), (names(), (mul, ()))))
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
def mul_(stack):
+    (a31, (a32, stack)) = stack
+    a33 = mul(a32, a31)
+    stack = (a33, stack)
+    return stack
+
+
+
+
+ +
+
+ +
+
+
+
In [76]:
+
+
+
e = (names(), (dup, (mul, ())))
+print compile_yinyang('sqr', e)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
def sqr(stack):
+    (a34, stack) = stack
+    a35 = mul(a34, a34)
+    stack = (a35, stack)
+    return stack
+
+
+
+
+ +
+
+ +
+
+
+
In [77]:
+
+
+
e = (names(), (dup, (names(), (sub, (mul, ())))))
+print compile_yinyang('foo', e)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
def foo(stack):
+    (a36, (a37, stack)) = stack
+    a38 = sub(a37, a36)
+    a39 = mul(a38, a36)
+    stack = (a39, stack)
+    return stack
+
+
+
+
+ +
+
+ +
+
+
+
In [78]:
+
+
+
e = (names(), (names(), (mul, (dup, (sub, (dup, ()))))))
+print compile_yinyang('bar', e)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
def bar(stack):
+    (a40, (a41, stack)) = stack
+    a42 = mul(a41, a40)
+    a43 = sub(a42, a42)
+    stack = (a43, (a43, stack))
+    return stack
+
+
+
+
+ +
+
+ +
+
+
+
In [79]:
+
+
+
e = (names(), (dup, (dup, (mul, (dup, (mul, (mul, ())))))))
+print compile_yinyang('to_the_fifth_power', e)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
def to_the_fifth_power(stack):
+    (a44, stack) = stack
+    a45 = mul(a44, a44)
+    a46 = mul(a45, a45)
+    a47 = mul(a46, a44)
+    stack = (a47, stack)
+    return stack
+
+
+
+
+ +
+
+ +
+
+
+ + + + + + diff --git a/docs/Compiling_Joy.ipynb b/docs/Compiling_Joy.ipynb new file mode 100644 index 0000000..34253bd --- /dev/null +++ b/docs/Compiling_Joy.ipynb @@ -0,0 +1,1310 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from notebook_preamble import D, J, V, define" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Compiling Joy\n", + "\n", + "Given a Joy program like:\n", + "\n", + " sqr == dup mul" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " . 23 sqr\n", + " 23 . sqr\n", + " 23 . dup mul\n", + "23 23 . mul\n", + " 529 . \n" + ] + } + ], + "source": [ + "V('23 sqr')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "How would we go about compiling this code (to Python for now)?\n", + "\n", + "## Naive Call Chaining\n", + "The simplest thing would be to compose the functions from the library:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "dup, mul = D['dup'], D['mul']" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def sqr(stack, expression, dictionary):\n", + " return mul(*dup(stack, expression, dictionary))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "old_sqr = D['sqr']\n", + "D['sqr'] = sqr" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " . 23 sqr\n", + " 23 . sqr\n", + "529 . \n" + ] + } + ], + "source": [ + "V('23 sqr')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It's simple to write a function to emit this kind of crude \"compiled\" code." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def compile_joy(name, expression):\n", + " term, expression = expression\n", + " code = term +'(stack, expression, dictionary)'\n", + " format_ = '%s(*%s)'\n", + " while expression:\n", + " term, expression = expression\n", + " code = format_ % (term, code)\n", + " return '''\\\n", + "def %s(stack, expression, dictionary):\n", + " return %s\n", + "''' % (name, code)\n", + "\n", + "\n", + "def compile_joy_definition(defi):\n", + " return compile_joy(defi.name, defi.body)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "def sqr(stack, expression, dictionary):\n", + " return mul(*dup(stack, expression, dictionary))\n", + "\n" + ] + } + ], + "source": [ + "print compile_joy_definition(old_sqr)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But what about literals?\n", + "\n", + " quoted == [unit] dip" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "unit, dip = D['unit'], D['dip']" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "# print compile_joy_definition(D['quoted'])\n", + "# raises\n", + "# TypeError: can only concatenate tuple (not \"str\") to tuple" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For a program like `foo == bar baz 23 99 baq lerp barp` we would want something like:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "def foo(stack, expression, dictionary):\n", + " stack, expression, dictionary = baz(*bar(stack, expression, dictionary))\n", + " return barp(*lerp(*baq((99, (23, stack)), expression, dictionary)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You have to have a little discontinuity when going from a symbol to a literal, because you have to pick out the stack from the arguments to push the literal(s) onto it before you continue chaining function calls." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compiling Yin Functions\n", + "Call-chaining results in code that does too much work. For functions that operate on stacks and only rearrange values, what I like to call \"Yin Functions\", we can do better.\n", + "\n", + "We can infer the stack effects of these functions (or \"expressions\" or \"programs\") automatically, and the stack effects completely define the semantics of the functions, so we can directly write out a two-line Python function for them. This is already implemented in the `joy.utils.types.compile_()` function." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "from joy.utils.types import compile_, doc_from_stack_effect, infer_string\n", + "from joy.library import SimpleFunctionWrapper" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "stack_effects = infer_string('tuck over dup')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Yin functions have only a single stack effect, they do not branch or loop." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(a2 a1 -- a1 a2 a1 a2 a2)\n" + ] + } + ], + "source": [ + "for fi, fo in stack_effects:\n", + " print doc_from_stack_effect(fi, fo)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "source = compile_('foo', stack_effects[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All Yin functions can be described in Python as a tuple-unpacking (or \"-destructuring\") of the stack datastructure followed by building up the new stack structure." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "def foo(stack):\n", + " \"\"\"\n", + " ::\n", + "\n", + " (a2 a1 -- a1 a2 a1 a2 a2)\n", + "\n", + " \"\"\"\n", + " (a1, (a2, s1)) = stack\n", + " return (a2, (a2, (a1, (a2, (a1, s1)))))\n" + ] + } + ], + "source": [ + "print source" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "exec compile(source, '__main__', 'single')\n", + "\n", + "D['foo'] = SimpleFunctionWrapper(foo)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " . 23 18 foo\n", + " 23 . 18 foo\n", + " 23 18 . foo\n", + "18 23 18 23 23 . \n" + ] + } + ], + "source": [ + "V('23 18 foo')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compiling from Stack Effects\n", + "\n", + "There are times when you're deriving a Joy program when you have a stack effect for a Yin function and you need to define it. For example, in the Ordered Binary Trees notebook there is a point where we must derive a function `Ee`:\n", + "\n", + " [key old_value left right] new_value key [Tree-add] Ee\n", + " ------------------------------------------------------------\n", + " [key new_value left right]\n", + "\n", + "While it is not hard to come up with this function manually, there is no necessity. This function can be defined (in Python) directly from its stack effect:\n", + "\n", + " [a b c d] e a [f] Ee\n", + " --------------------------\n", + " [a e c d]\n", + "\n", + "(I haven't yet implemented a simple interface for this yet. What follow is an exploration of how to do it.)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "from joy.parser import text_to_expression" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[a b c d] e a [f]] [[a e c d]]\n" + ] + } + ], + "source": [ + "Ein = '[a b c d] e a [f]' # The terms should be reversed here but I don't realize that until later.\n", + "Eout = '[a e c d]'\n", + "E = '[%s] [%s]' % (Ein, Eout)\n", + "\n", + "print E" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "(fi, (fo, _)) = text_to_expression(E)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(((a, (b, (c, (d, ())))), (e, (a, ((f, ()), ())))),\n", + " ((a, (e, (c, (d, ())))), ()))" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fi, fo" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[a1 a2 a3 a4] a5 a6 a7] [[a1 a5 a3 a4]]\n" + ] + } + ], + "source": [ + "Ein = '[a1 a2 a3 a4] a5 a6 a7'\n", + "Eout = '[a1 a5 a3 a4]'\n", + "E = '[%s] [%s]' % (Ein, Eout)\n", + "\n", + "print E" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "(fi, (fo, _)) = text_to_expression(E)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(((a1, (a2, (a3, (a4, ())))), (a5, (a6, (a7, ())))),\n", + " ((a1, (a5, (a3, (a4, ())))), ()))" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fi, fo" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'a1': a1,\n", + " 'a2': a2,\n", + " 'a3': a3,\n", + " 'a4': a4,\n", + " 'a5': a5,\n", + " 'a6': a6,\n", + " 'a7': a7,\n", + " 's0': s0,\n", + " 's1': s1}" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def type_vars():\n", + " from joy.library import a1, a2, a3, a4, a5, a6, a7, s0, s1\n", + " return locals()\n", + "\n", + "tv = type_vars()\n", + "tv" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "from joy.utils.types import reify" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(... a7 a6 a5 [a1 a2 a3 a4 ] -- ... [a1 a5 a3 a4 ])\n" + ] + } + ], + "source": [ + "stack_effect = reify(tv, (fi, fo))\n", + "print doc_from_stack_effect(*stack_effect)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(((a1, (a2, (a3, (a4, ())))), (a5, (a6, (a7, ())))), ((a1, (a5, (a3, (a4, ())))), ()))\n" + ] + } + ], + "source": [ + "print stack_effect" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Almost, but what we really want is something like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [], + "source": [ + "stack_effect = eval('(((a1, (a2, (a3, (a4, s1)))), (a5, (a6, (a7, s0)))), ((a1, (a5, (a3, (a4, s1)))), s0))', tv)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note the change of `()` to `JoyStackType` type variables." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(a7 a6 a5 [a1 a2 a3 a4 ...1] -- [a1 a5 a3 a4 ...1])\n" + ] + } + ], + "source": [ + "print doc_from_stack_effect(*stack_effect)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can omit `a3` and `a4` if we like:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [], + "source": [ + "stack_effect = eval('(((a1, (a2, s1)), (a5, (a6, (a7, s0)))), ((a1, (a5, s1)), s0))', tv)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `right` and `left` parts of the ordered binary tree node are subsumed in the tail of the node's stack/list." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(a7 a6 a5 [a1 a2 ...1] -- [a1 a5 ...1])\n" + ] + } + ], + "source": [ + "print doc_from_stack_effect(*stack_effect)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "def Ee(stack):\n", + " \"\"\"\n", + " ::\n", + "\n", + " (a7 a6 a5 [a1 a2 ...1] -- [a1 a5 ...1])\n", + "\n", + " \"\"\"\n", + " ((a1, (a2, s1)), (a5, (a6, (a7, s0)))) = stack\n", + " return ((a1, (a5, s1)), s0)\n" + ] + } + ], + "source": [ + "source = compile_('Ee', stack_effect)\n", + "print source" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Oops! The input stack is backwards..." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [], + "source": [ + "stack_effect = eval('((a7, (a6, (a5, ((a1, (a2, s1)), s0)))), ((a1, (a5, s1)), s0))', tv)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "([a1 a2 ...1] a5 a6 a7 -- [a1 a5 ...1])\n" + ] + } + ], + "source": [ + "print doc_from_stack_effect(*stack_effect)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "def Ee(stack):\n", + " \"\"\"\n", + " ::\n", + "\n", + " ([a1 a2 ...1] a5 a6 a7 -- [a1 a5 ...1])\n", + "\n", + " \"\"\"\n", + " (a7, (a6, (a5, ((a1, (a2, s1)), s0)))) = stack\n", + " return ((a1, (a5, s1)), s0)\n" + ] + } + ], + "source": [ + "source = compile_('Ee', stack_effect)\n", + "print source" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Compare:\n", + "\n", + " [key old_value left right] new_value key [Tree-add] Ee\n", + " ------------------------------------------------------------\n", + " [key new_value left right]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [], + "source": [ + "eval(compile(source, '__main__', 'single'))\n", + "D['Ee'] = SimpleFunctionWrapper(Ee)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " . [a b c d] 1 2 [f] Ee\n", + " [a b c d] . 1 2 [f] Ee\n", + " [a b c d] 1 . 2 [f] Ee\n", + " [a b c d] 1 2 . [f] Ee\n", + "[a b c d] 1 2 [f] . Ee\n", + " [a 1 c d] . \n" + ] + } + ], + "source": [ + "V('[a b c d] 1 2 [f] Ee')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Working with Yang Functions\n", + "\n", + "Consider the compiled code of `dup`:" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def dup(stack):\n", + " (a1, s23) = stack\n", + " return (a1, (a1, s23))\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To compile `sqr == dup mul` we can compute the stack effect:" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(n1 -- n2)\n" + ] + } + ], + "source": [ + "stack_effects = infer_string('dup mul')\n", + "for fi, fo in stack_effects:\n", + " print doc_from_stack_effect(fi, fo)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then we would want something like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def sqr(stack):\n", + " (n1, s23) = stack\n", + " n2 = mul(n1, n1)\n", + " return (n2, s23)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "How about..." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(n4 n3 n2 n1 -- n5)\n" + ] + } + ], + "source": [ + "stack_effects = infer_string('mul mul sub')\n", + "for fi, fo in stack_effects:\n", + " print doc_from_stack_effect(fi, fo)" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def foo(stack):\n", + " (n1, (n2, (n3, (n4, s23)))) = stack\n", + " n5 = mul(n1, n2)\n", + " n6 = mul(n5, n3)\n", + " n7 = sub(n6, n4)\n", + " return (n7, s23)\n", + "\n", + "\n", + "# or\n", + "\n", + "def foo(stack):\n", + " (n1, (n2, (n3, (n4, s23)))) = stack\n", + " n5 = sub(mul(mul(n1, n2), n3), n4)\n", + " return (n5, s23)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(a2 a1 -- a1 a2 a1)\n" + ] + } + ], + "source": [ + "stack_effects = infer_string('tuck')\n", + "for fi, fo in stack_effects:\n", + " print doc_from_stack_effect(fi, fo)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compiling Yin~Yang Functions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, we need a source of Python identifiers. I'm going to reuse `Symbol` class for this." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [], + "source": [ + "from joy.parser import Symbol" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [], + "source": [ + "def _names():\n", + " n = 0\n", + " while True:\n", + " yield Symbol('a' + str(n))\n", + " n += 1\n", + "\n", + "names = _names().next" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we need an object that represents a Yang function that accepts two args and return one result (we'll implement other kinds a little later.)" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": {}, + "outputs": [], + "source": [ + "class Foo(object):\n", + "\n", + " def __init__(self, name):\n", + " self.name = name\n", + "\n", + " def __call__(self, stack, expression, code):\n", + " in1, (in0, stack) = stack\n", + " out = names()\n", + " code.append(('call', out, self.name, (in0, in1)))\n", + " return (out, stack), expression, code" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A crude \"interpreter\" that translates expressions of args and Yin and Yang functions into a kind of simple dataflow graph." + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": {}, + "outputs": [], + "source": [ + "def I(stack, expression, code):\n", + " while expression:\n", + " term, expression = expression\n", + " if callable(term):\n", + " stack, expression, _ = term(stack, expression, code)\n", + " else:\n", + " stack = term, stack\n", + " code.append(('pop', term))\n", + "\n", + " s = []\n", + " while stack:\n", + " term, stack = stack\n", + " s.insert(0, term)\n", + " if s:\n", + " code.append(('push',) + tuple(s))\n", + " return code" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Something to convert the graph into Python code." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [], + "source": [ + "strtup = lambda a, b: '(%s, %s)' % (b, a)\n", + "strstk = lambda rest: reduce(strtup, rest, 'stack')\n", + "\n", + "\n", + "def code_gen(code):\n", + " coalesce_pops(code)\n", + " lines = []\n", + " for t in code:\n", + " tag, rest = t[0], t[1:]\n", + "\n", + " if tag == 'pop':\n", + " lines.append(strstk(rest) + ' = stack')\n", + "\n", + " elif tag == 'push':\n", + " lines.append('stack = ' + strstk(rest))\n", + "\n", + " elif tag == 'call':\n", + " #out, name, in_ = rest\n", + " lines.append('%s = %s%s' % rest)\n", + "\n", + " else:\n", + " raise ValueError(tag)\n", + "\n", + " return '\\n'.join(' ' + line for line in lines)\n", + "\n", + "\n", + "def coalesce_pops(code):\n", + " index = [i for i, t in enumerate(code) if t[0] == 'pop']\n", + " for start, end in yield_groups(index):\n", + " code[start:end] = \\\n", + " [tuple(['pop'] + [t for _, t in code[start:end][::-1]])]\n", + "\n", + "\n", + "def yield_groups(index):\n", + " '''\n", + " Yield slice indices for each group of contiguous ints in the\n", + " index list.\n", + " '''\n", + " k = 0\n", + " for i, (a, b) in enumerate(zip(index, index[1:])):\n", + " if b - a > 1:\n", + " if k != i:\n", + " yield index[k], index[i] + 1\n", + " k = i + 1\n", + " if k < len(index):\n", + " yield index[k], index[-1] + 1\n", + "\n", + "\n", + "def compile_yinyang(name, expression):\n", + " return '''\\\n", + "def %s(stack):\n", + "%s\n", + " return stack\n", + "''' % (name, code_gen(I((), expression, [])))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A few functions to try it with..." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": {}, + "outputs": [], + "source": [ + "mul = Foo('mul')\n", + "sub = Foo('sub')" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ":1: SyntaxWarning: import * only allowed at module level\n", + " def import_yin():\n" + ] + } + ], + "source": [ + "def import_yin():\n", + " from joy.utils.generated_library import *\n", + " return locals()\n", + "\n", + "yin_dict = {name: SimpleFunctionWrapper(func) for name, func in import_yin().iteritems()}\n", + "\n", + "yin_dict\n", + "\n", + "dup = yin_dict['dup']\n", + "\n", + "#def dup(stack, expression, code):\n", + "# n, stack = stack\n", + "# return (n, (n, stack)), expression" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and there we are." + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "def mul_(stack):\n", + " (a31, (a32, stack)) = stack\n", + " a33 = mul(a32, a31)\n", + " stack = (a33, stack)\n", + " return stack\n", + "\n" + ] + } + ], + "source": [ + "print compile_yinyang('mul_', (names(), (names(), (mul, ()))))" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "def sqr(stack):\n", + " (a34, stack) = stack\n", + " a35 = mul(a34, a34)\n", + " stack = (a35, stack)\n", + " return stack\n", + "\n" + ] + } + ], + "source": [ + "e = (names(), (dup, (mul, ())))\n", + "print compile_yinyang('sqr', e)" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "def foo(stack):\n", + " (a36, (a37, stack)) = stack\n", + " a38 = sub(a37, a36)\n", + " a39 = mul(a38, a36)\n", + " stack = (a39, stack)\n", + " return stack\n", + "\n" + ] + } + ], + "source": [ + "e = (names(), (dup, (names(), (sub, (mul, ())))))\n", + "print compile_yinyang('foo', e)" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "def bar(stack):\n", + " (a40, (a41, stack)) = stack\n", + " a42 = mul(a41, a40)\n", + " a43 = sub(a42, a42)\n", + " stack = (a43, (a43, stack))\n", + " return stack\n", + "\n" + ] + } + ], + "source": [ + "e = (names(), (names(), (mul, (dup, (sub, (dup, ()))))))\n", + "print compile_yinyang('bar', e)" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "def to_the_fifth_power(stack):\n", + " (a44, stack) = stack\n", + " a45 = mul(a44, a44)\n", + " a46 = mul(a45, a45)\n", + " a47 = mul(a46, a44)\n", + " stack = (a47, stack)\n", + " return stack\n", + "\n" + ] + } + ], + "source": [ + "e = (names(), (dup, (dup, (mul, (dup, (mul, (mul, ())))))))\n", + "print compile_yinyang('to_the_fifth_power', e)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.15" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/Compiling_Joy.md b/docs/Compiling_Joy.md new file mode 100644 index 0000000..061c413 --- /dev/null +++ b/docs/Compiling_Joy.md @@ -0,0 +1,724 @@ + + +```python +from notebook_preamble import D, J, V, define +``` + +# Compiling Joy + +Given a Joy program like: + + sqr == dup mul + + +```python +V('23 sqr') +``` + + . 23 sqr + 23 . sqr + 23 . dup mul + 23 23 . mul + 529 . + + +How would we go about compiling this code (to Python for now)? + +## Naive Call Chaining +The simplest thing would be to compose the functions from the library: + + +```python +dup, mul = D['dup'], D['mul'] +``` + + +```python +def sqr(stack, expression, dictionary): + return mul(*dup(stack, expression, dictionary)) +``` + + +```python +old_sqr = D['sqr'] +D['sqr'] = sqr +``` + + +```python +V('23 sqr') +``` + + . 23 sqr + 23 . sqr + 529 . + + +It's simple to write a function to emit this kind of crude "compiled" code. + + +```python +def compile_joy(name, expression): + term, expression = expression + code = term +'(stack, expression, dictionary)' + format_ = '%s(*%s)' + while expression: + term, expression = expression + code = format_ % (term, code) + return '''\ +def %s(stack, expression, dictionary): + return %s +''' % (name, code) + + +def compile_joy_definition(defi): + return compile_joy(defi.name, defi.body) + +``` + + +```python +print compile_joy_definition(old_sqr) +``` + + def sqr(stack, expression, dictionary): + return mul(*dup(stack, expression, dictionary)) + + + +But what about literals? + + quoted == [unit] dip + + +```python +unit, dip = D['unit'], D['dip'] +``` + + +```python +# print compile_joy_definition(D['quoted']) +# raises +# TypeError: can only concatenate tuple (not "str") to tuple +``` + +For a program like `foo == bar baz 23 99 baq lerp barp` we would want something like: + + +```python +def foo(stack, expression, dictionary): + stack, expression, dictionary = baz(*bar(stack, expression, dictionary)) + return barp(*lerp(*baq((99, (23, stack)), expression, dictionary))) +``` + +You have to have a little discontinuity when going from a symbol to a literal, because you have to pick out the stack from the arguments to push the literal(s) onto it before you continue chaining function calls. + +## Compiling Yin Functions +Call-chaining results in code that does too much work. For functions that operate on stacks and only rearrange values, what I like to call "Yin Functions", we can do better. + +We can infer the stack effects of these functions (or "expressions" or "programs") automatically, and the stack effects completely define the semantics of the functions, so we can directly write out a two-line Python function for them. This is already implemented in the `joy.utils.types.compile_()` function. + + +```python +from joy.utils.types import compile_, doc_from_stack_effect, infer_string +from joy.library import SimpleFunctionWrapper +``` + + +```python +stack_effects = infer_string('tuck over dup') +``` + +Yin functions have only a single stack effect, they do not branch or loop. + + +```python +for fi, fo in stack_effects: + print doc_from_stack_effect(fi, fo) +``` + + (a2 a1 -- a1 a2 a1 a2 a2) + + + +```python +source = compile_('foo', stack_effects[0]) +``` + +All Yin functions can be described in Python as a tuple-unpacking (or "-destructuring") of the stack datastructure followed by building up the new stack structure. + + +```python +print source +``` + + def foo(stack): + """ + :: + + (a2 a1 -- a1 a2 a1 a2 a2) + + """ + (a1, (a2, s1)) = stack + return (a2, (a2, (a1, (a2, (a1, s1))))) + + + +```python +exec compile(source, '__main__', 'single') + +D['foo'] = SimpleFunctionWrapper(foo) +``` + + +```python +V('23 18 foo') +``` + + . 23 18 foo + 23 . 18 foo + 23 18 . foo + 18 23 18 23 23 . + + +## Compiling from Stack Effects + +There are times when you're deriving a Joy program when you have a stack effect for a Yin function and you need to define it. For example, in the Ordered Binary Trees notebook there is a point where we must derive a function `Ee`: + + [key old_value left right] new_value key [Tree-add] Ee + ------------------------------------------------------------ + [key new_value left right] + +While it is not hard to come up with this function manually, there is no necessity. This function can be defined (in Python) directly from its stack effect: + + [a b c d] e a [f] Ee + -------------------------- + [a e c d] + +(I haven't yet implemented a simple interface for this yet. What follow is an exploration of how to do it.) + + +```python +from joy.parser import text_to_expression +``` + + +```python +Ein = '[a b c d] e a [f]' # The terms should be reversed here but I don't realize that until later. +Eout = '[a e c d]' +E = '[%s] [%s]' % (Ein, Eout) + +print E +``` + + [[a b c d] e a [f]] [[a e c d]] + + + +```python +(fi, (fo, _)) = text_to_expression(E) +``` + + +```python +fi, fo +``` + + + + + (((a, (b, (c, (d, ())))), (e, (a, ((f, ()), ())))), + ((a, (e, (c, (d, ())))), ())) + + + + +```python +Ein = '[a1 a2 a3 a4] a5 a6 a7' +Eout = '[a1 a5 a3 a4]' +E = '[%s] [%s]' % (Ein, Eout) + +print E +``` + + [[a1 a2 a3 a4] a5 a6 a7] [[a1 a5 a3 a4]] + + + +```python +(fi, (fo, _)) = text_to_expression(E) +``` + + +```python +fi, fo +``` + + + + + (((a1, (a2, (a3, (a4, ())))), (a5, (a6, (a7, ())))), + ((a1, (a5, (a3, (a4, ())))), ())) + + + + +```python +def type_vars(): + from joy.library import a1, a2, a3, a4, a5, a6, a7, s0, s1 + return locals() + +tv = type_vars() +tv +``` + + + + + {'a1': a1, + 'a2': a2, + 'a3': a3, + 'a4': a4, + 'a5': a5, + 'a6': a6, + 'a7': a7, + 's0': s0, + 's1': s1} + + + + +```python +from joy.utils.types import reify +``` + + +```python +stack_effect = reify(tv, (fi, fo)) +print doc_from_stack_effect(*stack_effect) +``` + + (... a7 a6 a5 [a1 a2 a3 a4 ] -- ... [a1 a5 a3 a4 ]) + + + +```python +print stack_effect +``` + + (((a1, (a2, (a3, (a4, ())))), (a5, (a6, (a7, ())))), ((a1, (a5, (a3, (a4, ())))), ())) + + +Almost, but what we really want is something like this: + + +```python +stack_effect = eval('(((a1, (a2, (a3, (a4, s1)))), (a5, (a6, (a7, s0)))), ((a1, (a5, (a3, (a4, s1)))), s0))', tv) +``` + +Note the change of `()` to `JoyStackType` type variables. + + +```python +print doc_from_stack_effect(*stack_effect) +``` + + (a7 a6 a5 [a1 a2 a3 a4 ...1] -- [a1 a5 a3 a4 ...1]) + + +Now we can omit `a3` and `a4` if we like: + + +```python +stack_effect = eval('(((a1, (a2, s1)), (a5, (a6, (a7, s0)))), ((a1, (a5, s1)), s0))', tv) +``` + +The `right` and `left` parts of the ordered binary tree node are subsumed in the tail of the node's stack/list. + + +```python +print doc_from_stack_effect(*stack_effect) +``` + + (a7 a6 a5 [a1 a2 ...1] -- [a1 a5 ...1]) + + + +```python +source = compile_('Ee', stack_effect) +print source +``` + + def Ee(stack): + """ + :: + + (a7 a6 a5 [a1 a2 ...1] -- [a1 a5 ...1]) + + """ + ((a1, (a2, s1)), (a5, (a6, (a7, s0)))) = stack + return ((a1, (a5, s1)), s0) + + +Oops! The input stack is backwards... + + +```python +stack_effect = eval('((a7, (a6, (a5, ((a1, (a2, s1)), s0)))), ((a1, (a5, s1)), s0))', tv) +``` + + +```python +print doc_from_stack_effect(*stack_effect) +``` + + ([a1 a2 ...1] a5 a6 a7 -- [a1 a5 ...1]) + + + +```python +source = compile_('Ee', stack_effect) +print source +``` + + def Ee(stack): + """ + :: + + ([a1 a2 ...1] a5 a6 a7 -- [a1 a5 ...1]) + + """ + (a7, (a6, (a5, ((a1, (a2, s1)), s0)))) = stack + return ((a1, (a5, s1)), s0) + + +Compare: + + [key old_value left right] new_value key [Tree-add] Ee + ------------------------------------------------------------ + [key new_value left right] + + + +```python +eval(compile(source, '__main__', 'single')) +D['Ee'] = SimpleFunctionWrapper(Ee) +``` + + +```python +V('[a b c d] 1 2 [f] Ee') +``` + + . [a b c d] 1 2 [f] Ee + [a b c d] . 1 2 [f] Ee + [a b c d] 1 . 2 [f] Ee + [a b c d] 1 2 . [f] Ee + [a b c d] 1 2 [f] . Ee + [a 1 c d] . + + +## Working with Yang Functions + +Consider the compiled code of `dup`: + + +```python + +def dup(stack): + (a1, s23) = stack + return (a1, (a1, s23)) + + +``` + +To compile `sqr == dup mul` we can compute the stack effect: + + +```python +stack_effects = infer_string('dup mul') +for fi, fo in stack_effects: + print doc_from_stack_effect(fi, fo) +``` + + (n1 -- n2) + + +Then we would want something like this: + + +```python + +def sqr(stack): + (n1, s23) = stack + n2 = mul(n1, n1) + return (n2, s23) + + +``` + +How about... + + +```python +stack_effects = infer_string('mul mul sub') +for fi, fo in stack_effects: + print doc_from_stack_effect(fi, fo) +``` + + (n4 n3 n2 n1 -- n5) + + + +```python + +def foo(stack): + (n1, (n2, (n3, (n4, s23)))) = stack + n5 = mul(n1, n2) + n6 = mul(n5, n3) + n7 = sub(n6, n4) + return (n7, s23) + + +# or + +def foo(stack): + (n1, (n2, (n3, (n4, s23)))) = stack + n5 = sub(mul(mul(n1, n2), n3), n4) + return (n5, s23) + + +``` + + +```python +stack_effects = infer_string('tuck') +for fi, fo in stack_effects: + print doc_from_stack_effect(fi, fo) +``` + + (a2 a1 -- a1 a2 a1) + + +## Compiling Yin~Yang Functions + +First, we need a source of Python identifiers. I'm going to reuse `Symbol` class for this. + + +```python +from joy.parser import Symbol +``` + + +```python +def _names(): + n = 0 + while True: + yield Symbol('a' + str(n)) + n += 1 + +names = _names().next +``` + +Now we need an object that represents a Yang function that accepts two args and return one result (we'll implement other kinds a little later.) + + +```python +class Foo(object): + + def __init__(self, name): + self.name = name + + def __call__(self, stack, expression, code): + in1, (in0, stack) = stack + out = names() + code.append(('call', out, self.name, (in0, in1))) + return (out, stack), expression, code +``` + +A crude "interpreter" that translates expressions of args and Yin and Yang functions into a kind of simple dataflow graph. + + +```python +def I(stack, expression, code): + while expression: + term, expression = expression + if callable(term): + stack, expression, _ = term(stack, expression, code) + else: + stack = term, stack + code.append(('pop', term)) + + s = [] + while stack: + term, stack = stack + s.insert(0, term) + if s: + code.append(('push',) + tuple(s)) + return code +``` + +Something to convert the graph into Python code. + + +```python +strtup = lambda a, b: '(%s, %s)' % (b, a) +strstk = lambda rest: reduce(strtup, rest, 'stack') + + +def code_gen(code): + coalesce_pops(code) + lines = [] + for t in code: + tag, rest = t[0], t[1:] + + if tag == 'pop': + lines.append(strstk(rest) + ' = stack') + + elif tag == 'push': + lines.append('stack = ' + strstk(rest)) + + elif tag == 'call': + #out, name, in_ = rest + lines.append('%s = %s%s' % rest) + + else: + raise ValueError(tag) + + return '\n'.join(' ' + line for line in lines) + + +def coalesce_pops(code): + index = [i for i, t in enumerate(code) if t[0] == 'pop'] + for start, end in yield_groups(index): + code[start:end] = \ + [tuple(['pop'] + [t for _, t in code[start:end][::-1]])] + + +def yield_groups(index): + ''' + Yield slice indices for each group of contiguous ints in the + index list. + ''' + k = 0 + for i, (a, b) in enumerate(zip(index, index[1:])): + if b - a > 1: + if k != i: + yield index[k], index[i] + 1 + k = i + 1 + if k < len(index): + yield index[k], index[-1] + 1 + + +def compile_yinyang(name, expression): + return '''\ +def %s(stack): +%s + return stack +''' % (name, code_gen(I((), expression, []))) + +``` + +A few functions to try it with... + + +```python +mul = Foo('mul') +sub = Foo('sub') +``` + + +```python +def import_yin(): + from joy.utils.generated_library import * + return locals() + +yin_dict = {name: SimpleFunctionWrapper(func) for name, func in import_yin().iteritems()} + +yin_dict + +dup = yin_dict['dup'] + +#def dup(stack, expression, code): +# n, stack = stack +# return (n, (n, stack)), expression +``` + + :1: SyntaxWarning: import * only allowed at module level + def import_yin(): + + +... and there we are. + + +```python +print compile_yinyang('mul_', (names(), (names(), (mul, ())))) +``` + + def mul_(stack): + (a31, (a32, stack)) = stack + a33 = mul(a32, a31) + stack = (a33, stack) + return stack + + + + +```python +e = (names(), (dup, (mul, ()))) +print compile_yinyang('sqr', e) +``` + + def sqr(stack): + (a34, stack) = stack + a35 = mul(a34, a34) + stack = (a35, stack) + return stack + + + + +```python +e = (names(), (dup, (names(), (sub, (mul, ()))))) +print compile_yinyang('foo', e) +``` + + def foo(stack): + (a36, (a37, stack)) = stack + a38 = sub(a37, a36) + a39 = mul(a38, a36) + stack = (a39, stack) + return stack + + + + +```python +e = (names(), (names(), (mul, (dup, (sub, (dup, ())))))) +print compile_yinyang('bar', e) +``` + + def bar(stack): + (a40, (a41, stack)) = stack + a42 = mul(a41, a40) + a43 = sub(a42, a42) + stack = (a43, (a43, stack)) + return stack + + + + +```python +e = (names(), (dup, (dup, (mul, (dup, (mul, (mul, ()))))))) +print compile_yinyang('to_the_fifth_power', e) +``` + + def to_the_fifth_power(stack): + (a44, stack) = stack + a45 = mul(a44, a44) + a46 = mul(a45, a45) + a47 = mul(a46, a44) + stack = (a47, stack) + return stack + + diff --git a/docs/Compiling_Joy.rst b/docs/Compiling_Joy.rst new file mode 100644 index 0000000..0f989e9 --- /dev/null +++ b/docs/Compiling_Joy.rst @@ -0,0 +1,788 @@ + +.. code:: ipython2 + + from notebook_preamble import D, J, V, define + +Compiling Joy +============= + +Given a Joy program like: + +:: + + sqr == dup mul + +.. code:: ipython2 + + V('23 sqr') + + +.. parsed-literal:: + + . 23 sqr + 23 . sqr + 23 . dup mul + 23 23 . mul + 529 . + + +How would we go about compiling this code (to Python for now)? + +Naive Call Chaining +------------------- + +The simplest thing would be to compose the functions from the library: + +.. code:: ipython2 + + dup, mul = D['dup'], D['mul'] + +.. code:: ipython2 + + def sqr(stack, expression, dictionary): + return mul(*dup(stack, expression, dictionary)) + +.. code:: ipython2 + + old_sqr = D['sqr'] + D['sqr'] = sqr + +.. code:: ipython2 + + V('23 sqr') + + +.. parsed-literal:: + + . 23 sqr + 23 . sqr + 529 . + + +It's simple to write a function to emit this kind of crude "compiled" +code. + +.. code:: ipython2 + + def compile_joy(name, expression): + term, expression = expression + code = term +'(stack, expression, dictionary)' + format_ = '%s(*%s)' + while expression: + term, expression = expression + code = format_ % (term, code) + return '''\ + def %s(stack, expression, dictionary): + return %s + ''' % (name, code) + + + def compile_joy_definition(defi): + return compile_joy(defi.name, defi.body) + + +.. code:: ipython2 + + print compile_joy_definition(old_sqr) + + +.. parsed-literal:: + + def sqr(stack, expression, dictionary): + return mul(*dup(stack, expression, dictionary)) + + + +But what about literals? + +:: + + quoted == [unit] dip + +.. code:: ipython2 + + unit, dip = D['unit'], D['dip'] + +.. code:: ipython2 + + # print compile_joy_definition(D['quoted']) + # raises + # TypeError: can only concatenate tuple (not "str") to tuple + +For a program like ``foo == bar baz 23 99 baq lerp barp`` we would want +something like: + +.. code:: ipython2 + + def foo(stack, expression, dictionary): + stack, expression, dictionary = baz(*bar(stack, expression, dictionary)) + return barp(*lerp(*baq((99, (23, stack)), expression, dictionary))) + +You have to have a little discontinuity when going from a symbol to a +literal, because you have to pick out the stack from the arguments to +push the literal(s) onto it before you continue chaining function calls. + +Compiling Yin Functions +----------------------- + +Call-chaining results in code that does too much work. For functions +that operate on stacks and only rearrange values, what I like to call +"Yin Functions", we can do better. + +We can infer the stack effects of these functions (or "expressions" or +"programs") automatically, and the stack effects completely define the +semantics of the functions, so we can directly write out a two-line +Python function for them. This is already implemented in the +``joy.utils.types.compile_()`` function. + +.. code:: ipython2 + + from joy.utils.types import compile_, doc_from_stack_effect, infer_string + from joy.library import SimpleFunctionWrapper + +.. code:: ipython2 + + stack_effects = infer_string('tuck over dup') + +Yin functions have only a single stack effect, they do not branch or +loop. + +.. code:: ipython2 + + for fi, fo in stack_effects: + print doc_from_stack_effect(fi, fo) + + +.. parsed-literal:: + + (a2 a1 -- a1 a2 a1 a2 a2) + + +.. code:: ipython2 + + source = compile_('foo', stack_effects[0]) + +All Yin functions can be described in Python as a tuple-unpacking (or +"-destructuring") of the stack datastructure followed by building up the +new stack structure. + +.. code:: ipython2 + + print source + + +.. parsed-literal:: + + def foo(stack): + """ + :: + + (a2 a1 -- a1 a2 a1 a2 a2) + + """ + (a1, (a2, s1)) = stack + return (a2, (a2, (a1, (a2, (a1, s1))))) + + +.. code:: ipython2 + + exec compile(source, '__main__', 'single') + + D['foo'] = SimpleFunctionWrapper(foo) + +.. code:: ipython2 + + V('23 18 foo') + + +.. parsed-literal:: + + . 23 18 foo + 23 . 18 foo + 23 18 . foo + 18 23 18 23 23 . + + +Compiling from Stack Effects +---------------------------- + +There are times when you're deriving a Joy program when you have a stack +effect for a Yin function and you need to define it. For example, in the +Ordered Binary Trees notebook there is a point where we must derive a +function ``Ee``: + +:: + + [key old_value left right] new_value key [Tree-add] Ee + ------------------------------------------------------------ + [key new_value left right] + +While it is not hard to come up with this function manually, there is no +necessity. This function can be defined (in Python) directly from its +stack effect: + +:: + + [a b c d] e a [f] Ee + -------------------------- + [a e c d] + +(I haven't yet implemented a simple interface for this yet. What follow +is an exploration of how to do it.) + +.. code:: ipython2 + + from joy.parser import text_to_expression + +.. code:: ipython2 + + Ein = '[a b c d] e a [f]' # The terms should be reversed here but I don't realize that until later. + Eout = '[a e c d]' + E = '[%s] [%s]' % (Ein, Eout) + + print E + + +.. parsed-literal:: + + [[a b c d] e a [f]] [[a e c d]] + + +.. code:: ipython2 + + (fi, (fo, _)) = text_to_expression(E) + +.. code:: ipython2 + + fi, fo + + + + +.. parsed-literal:: + + (((a, (b, (c, (d, ())))), (e, (a, ((f, ()), ())))), + ((a, (e, (c, (d, ())))), ())) + + + +.. code:: ipython2 + + Ein = '[a1 a2 a3 a4] a5 a6 a7' + Eout = '[a1 a5 a3 a4]' + E = '[%s] [%s]' % (Ein, Eout) + + print E + + +.. parsed-literal:: + + [[a1 a2 a3 a4] a5 a6 a7] [[a1 a5 a3 a4]] + + +.. code:: ipython2 + + (fi, (fo, _)) = text_to_expression(E) + +.. code:: ipython2 + + fi, fo + + + + +.. parsed-literal:: + + (((a1, (a2, (a3, (a4, ())))), (a5, (a6, (a7, ())))), + ((a1, (a5, (a3, (a4, ())))), ())) + + + +.. code:: ipython2 + + def type_vars(): + from joy.library import a1, a2, a3, a4, a5, a6, a7, s0, s1 + return locals() + + tv = type_vars() + tv + + + + +.. parsed-literal:: + + {'a1': a1, + 'a2': a2, + 'a3': a3, + 'a4': a4, + 'a5': a5, + 'a6': a6, + 'a7': a7, + 's0': s0, + 's1': s1} + + + +.. code:: ipython2 + + from joy.utils.types import reify + +.. code:: ipython2 + + stack_effect = reify(tv, (fi, fo)) + print doc_from_stack_effect(*stack_effect) + + +.. parsed-literal:: + + (... a7 a6 a5 [a1 a2 a3 a4 ] -- ... [a1 a5 a3 a4 ]) + + +.. code:: ipython2 + + print stack_effect + + +.. parsed-literal:: + + (((a1, (a2, (a3, (a4, ())))), (a5, (a6, (a7, ())))), ((a1, (a5, (a3, (a4, ())))), ())) + + +Almost, but what we really want is something like this: + +.. code:: ipython2 + + stack_effect = eval('(((a1, (a2, (a3, (a4, s1)))), (a5, (a6, (a7, s0)))), ((a1, (a5, (a3, (a4, s1)))), s0))', tv) + +Note the change of ``()`` to ``JoyStackType`` type variables. + +.. code:: ipython2 + + print doc_from_stack_effect(*stack_effect) + + +.. parsed-literal:: + + (a7 a6 a5 [a1 a2 a3 a4 ...1] -- [a1 a5 a3 a4 ...1]) + + +Now we can omit ``a3`` and ``a4`` if we like: + +.. code:: ipython2 + + stack_effect = eval('(((a1, (a2, s1)), (a5, (a6, (a7, s0)))), ((a1, (a5, s1)), s0))', tv) + +The ``right`` and ``left`` parts of the ordered binary tree node are +subsumed in the tail of the node's stack/list. + +.. code:: ipython2 + + print doc_from_stack_effect(*stack_effect) + + +.. parsed-literal:: + + (a7 a6 a5 [a1 a2 ...1] -- [a1 a5 ...1]) + + +.. code:: ipython2 + + source = compile_('Ee', stack_effect) + print source + + +.. parsed-literal:: + + def Ee(stack): + """ + :: + + (a7 a6 a5 [a1 a2 ...1] -- [a1 a5 ...1]) + + """ + ((a1, (a2, s1)), (a5, (a6, (a7, s0)))) = stack + return ((a1, (a5, s1)), s0) + + +Oops! The input stack is backwards... + +.. code:: ipython2 + + stack_effect = eval('((a7, (a6, (a5, ((a1, (a2, s1)), s0)))), ((a1, (a5, s1)), s0))', tv) + +.. code:: ipython2 + + print doc_from_stack_effect(*stack_effect) + + +.. parsed-literal:: + + ([a1 a2 ...1] a5 a6 a7 -- [a1 a5 ...1]) + + +.. code:: ipython2 + + source = compile_('Ee', stack_effect) + print source + + +.. parsed-literal:: + + def Ee(stack): + """ + :: + + ([a1 a2 ...1] a5 a6 a7 -- [a1 a5 ...1]) + + """ + (a7, (a6, (a5, ((a1, (a2, s1)), s0)))) = stack + return ((a1, (a5, s1)), s0) + + +Compare: + +:: + + [key old_value left right] new_value key [Tree-add] Ee + ------------------------------------------------------------ + [key new_value left right] + +.. code:: ipython2 + + eval(compile(source, '__main__', 'single')) + D['Ee'] = SimpleFunctionWrapper(Ee) + +.. code:: ipython2 + + V('[a b c d] 1 2 [f] Ee') + + +.. parsed-literal:: + + . [a b c d] 1 2 [f] Ee + [a b c d] . 1 2 [f] Ee + [a b c d] 1 . 2 [f] Ee + [a b c d] 1 2 . [f] Ee + [a b c d] 1 2 [f] . Ee + [a 1 c d] . + + +Working with Yang Functions +--------------------------- + +Consider the compiled code of ``dup``: + +.. code:: ipython2 + + + def dup(stack): + (a1, s23) = stack + return (a1, (a1, s23)) + + + +To compile ``sqr == dup mul`` we can compute the stack effect: + +.. code:: ipython2 + + stack_effects = infer_string('dup mul') + for fi, fo in stack_effects: + print doc_from_stack_effect(fi, fo) + + +.. parsed-literal:: + + (n1 -- n2) + + +Then we would want something like this: + +.. code:: ipython2 + + + def sqr(stack): + (n1, s23) = stack + n2 = mul(n1, n1) + return (n2, s23) + + + +How about... + +.. code:: ipython2 + + stack_effects = infer_string('mul mul sub') + for fi, fo in stack_effects: + print doc_from_stack_effect(fi, fo) + + +.. parsed-literal:: + + (n4 n3 n2 n1 -- n5) + + +.. code:: ipython2 + + + def foo(stack): + (n1, (n2, (n3, (n4, s23)))) = stack + n5 = mul(n1, n2) + n6 = mul(n5, n3) + n7 = sub(n6, n4) + return (n7, s23) + + + # or + + def foo(stack): + (n1, (n2, (n3, (n4, s23)))) = stack + n5 = sub(mul(mul(n1, n2), n3), n4) + return (n5, s23) + + + +.. code:: ipython2 + + stack_effects = infer_string('tuck') + for fi, fo in stack_effects: + print doc_from_stack_effect(fi, fo) + + +.. parsed-literal:: + + (a2 a1 -- a1 a2 a1) + + +Compiling Yin~Yang Functions +---------------------------- + +First, we need a source of Python identifiers. I'm going to reuse +``Symbol`` class for this. + +.. code:: ipython2 + + from joy.parser import Symbol + +.. code:: ipython2 + + def _names(): + n = 0 + while True: + yield Symbol('a' + str(n)) + n += 1 + + names = _names().next + +Now we need an object that represents a Yang function that accepts two +args and return one result (we'll implement other kinds a little later.) + +.. code:: ipython2 + + class Foo(object): + + def __init__(self, name): + self.name = name + + def __call__(self, stack, expression, code): + in1, (in0, stack) = stack + out = names() + code.append(('call', out, self.name, (in0, in1))) + return (out, stack), expression, code + +A crude "interpreter" that translates expressions of args and Yin and +Yang functions into a kind of simple dataflow graph. + +.. code:: ipython2 + + def I(stack, expression, code): + while expression: + term, expression = expression + if callable(term): + stack, expression, _ = term(stack, expression, code) + else: + stack = term, stack + code.append(('pop', term)) + + s = [] + while stack: + term, stack = stack + s.insert(0, term) + if s: + code.append(('push',) + tuple(s)) + return code + +Something to convert the graph into Python code. + +.. code:: ipython2 + + strtup = lambda a, b: '(%s, %s)' % (b, a) + strstk = lambda rest: reduce(strtup, rest, 'stack') + + + def code_gen(code): + coalesce_pops(code) + lines = [] + for t in code: + tag, rest = t[0], t[1:] + + if tag == 'pop': + lines.append(strstk(rest) + ' = stack') + + elif tag == 'push': + lines.append('stack = ' + strstk(rest)) + + elif tag == 'call': + #out, name, in_ = rest + lines.append('%s = %s%s' % rest) + + else: + raise ValueError(tag) + + return '\n'.join(' ' + line for line in lines) + + + def coalesce_pops(code): + index = [i for i, t in enumerate(code) if t[0] == 'pop'] + for start, end in yield_groups(index): + code[start:end] = \ + [tuple(['pop'] + [t for _, t in code[start:end][::-1]])] + + + def yield_groups(index): + ''' + Yield slice indices for each group of contiguous ints in the + index list. + ''' + k = 0 + for i, (a, b) in enumerate(zip(index, index[1:])): + if b - a > 1: + if k != i: + yield index[k], index[i] + 1 + k = i + 1 + if k < len(index): + yield index[k], index[-1] + 1 + + + def compile_yinyang(name, expression): + return '''\ + def %s(stack): + %s + return stack + ''' % (name, code_gen(I((), expression, []))) + + +A few functions to try it with... + +.. code:: ipython2 + + mul = Foo('mul') + sub = Foo('sub') + +.. code:: ipython2 + + def import_yin(): + from joy.utils.generated_library import * + return locals() + + yin_dict = {name: SimpleFunctionWrapper(func) for name, func in import_yin().iteritems()} + + yin_dict + + dup = yin_dict['dup'] + + #def dup(stack, expression, code): + # n, stack = stack + # return (n, (n, stack)), expression + + +.. parsed-literal:: + + :1: SyntaxWarning: import * only allowed at module level + def import_yin(): + + +... and there we are. + +.. code:: ipython2 + + print compile_yinyang('mul_', (names(), (names(), (mul, ())))) + + +.. parsed-literal:: + + def mul_(stack): + (a31, (a32, stack)) = stack + a33 = mul(a32, a31) + stack = (a33, stack) + return stack + + + +.. code:: ipython2 + + e = (names(), (dup, (mul, ()))) + print compile_yinyang('sqr', e) + + +.. parsed-literal:: + + def sqr(stack): + (a34, stack) = stack + a35 = mul(a34, a34) + stack = (a35, stack) + return stack + + + +.. code:: ipython2 + + e = (names(), (dup, (names(), (sub, (mul, ()))))) + print compile_yinyang('foo', e) + + +.. parsed-literal:: + + def foo(stack): + (a36, (a37, stack)) = stack + a38 = sub(a37, a36) + a39 = mul(a38, a36) + stack = (a39, stack) + return stack + + + +.. code:: ipython2 + + e = (names(), (names(), (mul, (dup, (sub, (dup, ())))))) + print compile_yinyang('bar', e) + + +.. parsed-literal:: + + def bar(stack): + (a40, (a41, stack)) = stack + a42 = mul(a41, a40) + a43 = sub(a42, a42) + stack = (a43, (a43, stack)) + return stack + + + +.. code:: ipython2 + + e = (names(), (dup, (dup, (mul, (dup, (mul, (mul, ()))))))) + print compile_yinyang('to_the_fifth_power', e) + + +.. parsed-literal:: + + def to_the_fifth_power(stack): + (a44, stack) = stack + a45 = mul(a44, a44) + a46 = mul(a45, a45) + a47 = mul(a46, a44) + stack = (a47, stack) + return stack + + diff --git a/docs/fun_with_scan.html b/docs/fun_with_scan.html new file mode 100644 index 0000000..44733f8 --- /dev/null +++ b/docs/fun_with_scan.html @@ -0,0 +1,12319 @@ + + + +fun_with_scan + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
In [1]:
+
+
+
from notebook_preamble import D, DefinitionWrapper, J, V, define
+
+ +
+
+
+ +
+
+
+
+
+

On "Two Exercises Found in a Book on Algorithmics"

Bird & Meertens

+ +
+
+
+
+
+
+
+

Define scan in terms of a reduction.

Problem I. The reduction operator / of APL takes some binary operator on its left and a vector x of values on its right. The meaning of ⨁/x for x = [a b ... z] is the value a⨁b⨁...⨁z. For this to be well-defined in the absence of brackets, the operation has to be associative. Now there is another operator \ of APL called scan. Its effect is closely related to reduction in that we have:

+
+ +
⨁\x = [a a⨁b a⨁b⨁c ... a⨁b⨁...⨁z]
+
+
+

The problem is to find some definition of scan as a reduction. In other words, we have to find some function f and an operator so that

+
+ +
⨂\x = f(a)⨂f(b)⨂...⨂f(z)
+ +
+
+
+
+
+
+
+

Designing the Recursive Function

Ignoring the exact requirements (finding f and ) can we implement scan as a hylomorphism in Joy?

+

Looking at the forms of hylomorphism, H3 is the one to use:

+ +
+
+
+
+
+
+
+

H3

If the combiner and the generator both need to work on the current value then dup must be used, and the generator must produce one item instead of two (the b is instead the duplicate of a.)

+ +
H3 == [P] [pop c] [[G] dupdip] [dip F] genrec
+
+... a [G] dupdip [H3] dip F
+... a  G  a      [H3] dip F
+... a′    a      [H3] dip F
+... a′ H3 a               F
+... a′ [G] dupdip [H3] dip F a F
+... a′  G  a′     [H3] dip F a F
+... a″     a′     [H3] dip F a F
+... a″ H3  a′              F a F
+... a″ [G] dupdip [H3] dip F a′ F a F
+... a″  G    a″   [H3] dip F a′ F a F
+... a‴       a″   [H3] dip F a′ F a F
+... a‴ H3    a″            F a′ F a F
+... a‴ pop c a″ F a′ F a F
+...        c a″ F a′ F a F
+...        d      a′ F a F
+...        d′          a F
+...        d″
+ +
+
+
+
+
+
+
+

Initial Definition

We're building a list of values so this is an "anamorphism". (An anamorphism uses [] for c and swons for F.)

+ +
scan == [P] [pop []] [[G] dupdip]      [dip swons] genrec
+
+
+

Convert to ifte:

+ +
scan == [P] [pop []] [[G] dupdip [scan] dip swons] ifte
+ +
+
+
+
+
+
+
+

On the recursive branch [G] dupdip doesn't cut it:

+ +
[1 2 3] [G] dupdip [scan] dip swons
+[1 2 3]  G [1 2 3] [scan] dip swons
+ +
+
+
+
+
+
+
+

Use first

At this point, we want the copy of [1 2 3] to just be 1, so we use first.

+ +
scan == [P] [pop []] [[G] dupdip first] [dip swons] genrec
+
+[1 2 3] [G] dupdip first [scan] dip swons
+[1 2 3]  G [1 2 3] first [scan] dip swons
+[1 2 3]  G  1            [scan] dip swons
+ +
+
+
+
+
+
+
+

G applies

Now what does G have to do? Just apply to the first two terms in the list.

+ +
[1 2 3] G
+[1 2 3] [⨁] infra
+[1 2 3] [+] infra
+[3 3]
+ +
+
+
+
+
+
+
+

Predicate P

Which tells us that the predicate [P] must guard against lists with less that two items in them:

+ +
P == size 1 <=
+ +
+
+
+
+
+
+
+

Let's see what we've got so far:

+ +
scan == [P        ] [pop []] [[G]         dupdip first] [dip swons] genrec
+scan == [size 1 <=] [pop []] [[[F] infra] dupdip first] [dip swons] genrec
+ +
+
+
+
+
+
+
+

Handling the Last Term

This works to a point, but it throws away the last term:

+ +
+
+
+
+
+
In [2]:
+
+
+
J('[1 2 3] [size 1 <=] [pop []] [[[+] infra] dupdip first] [dip swons] genrec')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[1 3]
+
+
+
+ +
+
+ +
+
+
+
+
+

Hmm... Let's take out the pop for a sec...

+ +
+
+
+
+
+
In [3]:
+
+
+
J('[1 2 3] [size 1 <=] [[]] [[[+] infra] dupdip first] [dip swons] genrec')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[6] [1 3]
+
+
+
+ +
+
+ +
+
+
+
+
+

That leaves the last item in our list, then it puts an empty list on the stack and swons's the new terms onto that. If we leave out that empty list, they will be swons'd onto that list that already has the last item.

+ +
+
+
+
+
+
In [4]:
+
+
+
J('[1 2 3] [size 1 <=] [] [[[+] infra] dupdip first] [dip swons] genrec')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[1 3 6]
+
+
+
+ +
+
+ +
+
+
+
+
+

Parameterize

So we have:

+ +
[⨁] scan == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec
+
+
+

Trivially:

+ +
 == [size 1 <=] [] [[[⨁] infra] dupdip first]                 [dip swons] genrec
+ == [[[⨁] infra] dupdip first]           [size 1 <=] [] roll< [dip swons] genrec
+ == [[⨁] infra]      [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
+ == [⨁] [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
+
+
+

And so:

+ +
scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec
+ +
+
+
+
+
+
In [5]:
+
+
+
define('scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec')
+
+ +
+
+
+ +
+
+
+
In [6]:
+
+
+
J('[1 2 3 4] [+] scan')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[1 3 6 10]
+
+
+
+ +
+
+ +
+
+
+
In [7]:
+
+
+
J('[1 2 3 4] [*] scan')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[1 2 6 24]
+
+
+
+ +
+
+ +
+
+
+
In [8]:
+
+
+
J('[1 2 3 4 5 6 7] [neg +] scan')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[1 1 2 2 3 3 4]
+
+
+
+ +
+
+ +
+
+
+
+
+

Problem 2.

Define a line to be a sequence of characters not containing the newline character. It is easy to define a function Unlines that converts a non-empty sequence of lines into a sequence of characters by inserting newline characters between every two lines.

+

Since Unlines is injective, the function Lines, which converts a sequence of characters into a sequence of lines by splitting on newline characters, can be specified as the inverse of Unlines.

+

The problem, just as in Problem 1. is to find a definition by reduction of the function Lines.

+
+ +
Unlines = uncons ['\n' swap + +] step
+ +
+
+
+
+
+
In [10]:
+
+
+
J('["hello" "world"] uncons ["\n" swap + +] step')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
'hello\nworld'
+
+
+
+ +
+
+ +
+
+
+
+
+

Again ignoring the actual task let's just derive Lines:

+ +
   "abc\nefg\nhij" Lines
+---------------------------
+    ["abc" "efg" "hij"]
+ +
+
+
+
+
+
+
+

Instead of P == [size 1 <=] we want ["\n" in], and for the base-case of a string with no newlines in it we want to use unit:

+ +
Lines == ["\n" in] [unit] [R0]       [dip swons] genrec
+Lines == ["\n" in] [unit] [R0 [Lines] dip swons] ifte
+ +
+
+
+
+
+
+
+

Derive R0:

+ +
"a \n b" R0                    [Lines] dip swons
+"a \n b" split-at-newline swap [Lines] dip swons
+"a " " b"                 swap [Lines] dip swons
+" b" "a "                      [Lines] dip swons
+" b" Lines "a " swons
+[" b"]     "a " swons
+["a " " b"]
+ +
+
+
+
+
+
+
+

So:

+ +
R0 == split-at-newline swap
+
+Lines == ["\n" in] [unit] [split-at-newline swap] [dip swons] genrec
+ +
+
+
+
+
+
+
+

Missing the Point?

This is all good and well, but in the paper many interesting laws and properties are discussed. Am I missing the point?

+ +
0 [a b c d] [F] step == 0 [a b] [F] step 0 [c d] [F] step concat
+
+
+

For associative function F and a "unit" element for that function, here represented by 0.

+

For functions that don't have a "unit" we can fake it (the example is given of infinity for the min(a, b) function.) We can also use:

+ +
safe_step == [size 1 <=] [] [uncons [F] step] ifte
+
+
+

Or:

+ +
safe_step == [pop size 1 <=] [pop] [[uncons] dip step] ifte
+
+   [a b c] [F] safe_step
+---------------------------
+   a [b c] [F] step
+
+
+

To limit F to working on pairs of terms from its domain.

+ +
+
+
+
+
+ + + + + + diff --git a/docs/fun_with_scan.ipynb b/docs/fun_with_scan.ipynb new file mode 100644 index 0000000..5583168 --- /dev/null +++ b/docs/fun_with_scan.ipynb @@ -0,0 +1,441 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from notebook_preamble import D, DefinitionWrapper, J, V, define" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# On \"Two Exercises Found in a Book on Algorithmics\"\n", + "\n", + "Bird & Meertens" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define `scan` in terms of a reduction.\n", + "\n", + "> Problem I. The reduction operator `/` of APL takes some binary operator `⨁` on its left and a vector `x` of values on its right. The meaning of `⨁/x` for `x = [a b ... z]` is the value `a⨁b⨁...⨁z`. For this to be well-defined in the absence of brackets, the operation `⨁` has to be associative. Now there is another operator `\\` of APL called `scan`. Its effect is closely related to reduction in that we have:\n", + "\n", + " ⨁\\x = [a a⨁b a⨁b⨁c ... a⨁b⨁...⨁z]\n", + "\n", + "> The problem is to find some definition of `scan` as a reduction. In other words, we have to find some function `f` and an operator `⨂` so that\n", + "\n", + " ⨂\\x = f(a)⨂f(b)⨂...⨂f(z)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Designing the Recursive Function\n", + "Ignoring the exact requirements (finding `f` and `⨂`) can we implement `scan` as a hylomorphism in Joy?\n", + "\n", + "Looking at the forms of hylomorphism, `H3` is the one to use:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### `H3`\n", + "If the combiner and the generator both need to work on the current value then `dup` must be used, and the generator must produce one item instead of two (the b is instead the duplicate of a.)\n", + "\n", + "\n", + " H3 == [P] [pop c] [[G] dupdip] [dip F] genrec\n", + "\n", + " ... a [G] dupdip [H3] dip F\n", + " ... a G a [H3] dip F\n", + " ... a′ a [H3] dip F\n", + " ... a′ H3 a F\n", + " ... a′ [G] dupdip [H3] dip F a F\n", + " ... a′ G a′ [H3] dip F a F\n", + " ... a″ a′ [H3] dip F a F\n", + " ... a″ H3 a′ F a F\n", + " ... a″ [G] dupdip [H3] dip F a′ F a F\n", + " ... a″ G a″ [H3] dip F a′ F a F\n", + " ... a‴ a″ [H3] dip F a′ F a F\n", + " ... a‴ H3 a″ F a′ F a F\n", + " ... a‴ pop c a″ F a′ F a F\n", + " ... c a″ F a′ F a F\n", + " ... d a′ F a F\n", + " ... d′ a F\n", + " ... d″" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initial Definition\n", + "We're building a list of values so this is an \"anamorphism\". (An anamorphism uses `[]` for `c` and `swons` for `F`.)\n", + "\n", + " scan == [P] [pop []] [[G] dupdip] [dip swons] genrec\n", + "\n", + "Convert to `ifte`:\n", + "\n", + " scan == [P] [pop []] [[G] dupdip [scan] dip swons] ifte" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On the recursive branch `[G] dupdip` doesn't cut it:\n", + "\n", + " [1 2 3] [G] dupdip [scan] dip swons\n", + " [1 2 3] G [1 2 3] [scan] dip swons" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Use `first`\n", + "At this point, we want the copy of `[1 2 3]` to just be `1`, so we use `first`.\n", + "\n", + " scan == [P] [pop []] [[G] dupdip first] [dip swons] genrec\n", + "\n", + " [1 2 3] [G] dupdip first [scan] dip swons\n", + " [1 2 3] G [1 2 3] first [scan] dip swons\n", + " [1 2 3] G 1 [scan] dip swons" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### `G` applies `⨁`\n", + "Now what does `G` have to do? Just apply `⨁` to the first two terms in the list.\n", + "\n", + " [1 2 3] G\n", + " [1 2 3] [⨁] infra\n", + " [1 2 3] [+] infra\n", + " [3 3]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Predicate `P`\n", + "Which tells us that the predicate `[P]` must guard against lists with less that two items in them:\n", + "\n", + " P == size 1 <=" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's see what we've got so far:\n", + "\n", + " scan == [P ] [pop []] [[G] dupdip first] [dip swons] genrec\n", + " scan == [size 1 <=] [pop []] [[[F] infra] dupdip first] [dip swons] genrec" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Handling the Last Term\n", + "This works to a point, but it throws away the last term:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1 3]\n" + ] + } + ], + "source": [ + "J('[1 2 3] [size 1 <=] [pop []] [[[+] infra] dupdip first] [dip swons] genrec')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Hmm... Let's take out the `pop` for a sec..." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[6] [1 3]\n" + ] + } + ], + "source": [ + "J('[1 2 3] [size 1 <=] [[]] [[[+] infra] dupdip first] [dip swons] genrec')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That leaves the last item in our list, then it puts an empty list on the stack and `swons`'s the new terms onto that. If we leave out that empty list, they will be `swons`'d onto that list that already has the last item." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1 3 6]\n" + ] + } + ], + "source": [ + "J('[1 2 3] [size 1 <=] [] [[[+] infra] dupdip first] [dip swons] genrec')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Parameterize `⨁`\n", + "So we have:\n", + "\n", + " [⨁] scan == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec\n", + "\n", + "Trivially:\n", + "\n", + " == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec\n", + " == [[[⨁] infra] dupdip first] [size 1 <=] [] roll< [dip swons] genrec\n", + " == [[⨁] infra] [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec\n", + " == [⨁] [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec\n", + "\n", + "And so:\n", + "\n", + " scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "define('scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1 3 6 10]\n" + ] + } + ], + "source": [ + "J('[1 2 3 4] [+] scan')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1 2 6 24]\n" + ] + } + ], + "source": [ + "J('[1 2 3 4] [*] scan')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1 1 2 2 3 3 4]\n" + ] + } + ], + "source": [ + "J('[1 2 3 4 5 6 7] [neg +] scan')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Problem 2.\n", + "> Define a line to be a sequence of characters not containing the newline character. It is easy to define a function `Unlines` that converts a non-empty sequence of lines into a sequence of characters by inserting newline characters between every two lines.\n", + ">\n", + "> Since `Unlines` is injective, the function `Lines`, which converts a sequence of characters into a sequence of lines by splitting on newline characters, can be specified as the inverse of `Unlines`.\n", + ">\n", + "> The problem, just as in Problem 1. is to find a definition by reduction of the function `Lines`.\n", + "\n", + "\n", + " Unlines = uncons ['\\n' swap + +] step\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "'hello\\nworld'\n" + ] + } + ], + "source": [ + "J('[\"hello\" \"world\"] uncons [\"\\n\" swap + +] step')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again ignoring the actual task let's just derive `Lines`:\n", + "\n", + " \"abc\\nefg\\nhij\" Lines\n", + " ---------------------------\n", + " [\"abc\" \"efg\" \"hij\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Instead of `P == [size 1 <=]` we want `[\"\\n\" in]`, and for the base-case of a string with no newlines in it we want to use `unit`:\n", + "\n", + " Lines == [\"\\n\" in] [unit] [R0] [dip swons] genrec\n", + " Lines == [\"\\n\" in] [unit] [R0 [Lines] dip swons] ifte" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Derive `R0`:\n", + "\n", + " \"a \\n b\" R0 [Lines] dip swons\n", + " \"a \\n b\" split-at-newline swap [Lines] dip swons\n", + " \"a \" \" b\" swap [Lines] dip swons\n", + " \" b\" \"a \" [Lines] dip swons\n", + " \" b\" Lines \"a \" swons\n", + " [\" b\"] \"a \" swons\n", + " [\"a \" \" b\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So:\n", + "\n", + " R0 == split-at-newline swap\n", + "\n", + " Lines == [\"\\n\" in] [unit] [split-at-newline swap] [dip swons] genrec" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Missing the Point?\n", + "This is all good and well, but in the paper many interesting laws and properties are discussed. Am I missing the point?\n", + "\n", + " 0 [a b c d] [F] step == 0 [a b] [F] step 0 [c d] [F] step concat\n", + "\n", + "For associative function `F` and a \"unit\" element for that function, here represented by `0`.\n", + "\n", + "For functions that don't have a \"unit\" we can fake it (the example is given of infinity for the `min(a, b)` function.) We can also use:\n", + "\n", + " safe_step == [size 1 <=] [] [uncons [F] step] ifte\n", + "\n", + "Or:\n", + "\n", + " safe_step == [pop size 1 <=] [pop] [[uncons] dip step] ifte\n", + "\n", + " [a b c] [F] safe_step\n", + " ---------------------------\n", + " a [b c] [F] step\n", + "\n", + "To limit `F` to working on pairs of terms from its domain.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.15" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/fun_with_scan.md b/docs/fun_with_scan.md new file mode 100644 index 0000000..5e0cf67 --- /dev/null +++ b/docs/fun_with_scan.md @@ -0,0 +1,234 @@ + + +```python +from notebook_preamble import D, DefinitionWrapper, J, V, define +``` + +# On "Two Exercises Found in a Book on Algorithmics" + +Bird & Meertens + +## Define `scan` in terms of a reduction. + +> Problem I. The reduction operator `/` of APL takes some binary operator `⨁` on its left and a vector `x` of values on its right. The meaning of `⨁/x` for `x = [a b ... z]` is the value `a⨁b⨁...⨁z`. For this to be well-defined in the absence of brackets, the operation `⨁` has to be associative. Now there is another operator `\` of APL called `scan`. Its effect is closely related to reduction in that we have: + + ⨁\x = [a a⨁b a⨁b⨁c ... a⨁b⨁...⨁z] + +> The problem is to find some definition of `scan` as a reduction. In other words, we have to find some function `f` and an operator `⨂` so that + + ⨂\x = f(a)⨂f(b)⨂...⨂f(z) + +## Designing the Recursive Function +Ignoring the exact requirements (finding `f` and `⨂`) can we implement `scan` as a hylomorphism in Joy? + +Looking at the forms of hylomorphism, `H3` is the one to use: + +### `H3` +If the combiner and the generator both need to work on the current value then `dup` must be used, and the generator must produce one item instead of two (the b is instead the duplicate of a.) + + + H3 == [P] [pop c] [[G] dupdip] [dip F] genrec + + ... a [G] dupdip [H3] dip F + ... a G a [H3] dip F + ... a′ a [H3] dip F + ... a′ H3 a F + ... a′ [G] dupdip [H3] dip F a F + ... a′ G a′ [H3] dip F a F + ... a″ a′ [H3] dip F a F + ... a″ H3 a′ F a F + ... a″ [G] dupdip [H3] dip F a′ F a F + ... a″ G a″ [H3] dip F a′ F a F + ... a‴ a″ [H3] dip F a′ F a F + ... a‴ H3 a″ F a′ F a F + ... a‴ pop c a″ F a′ F a F + ... c a″ F a′ F a F + ... d a′ F a F + ... d′ a F + ... d″ + +### Initial Definition +We're building a list of values so this is an "anamorphism". (An anamorphism uses `[]` for `c` and `swons` for `F`.) + + scan == [P] [pop []] [[G] dupdip] [dip swons] genrec + +Convert to `ifte`: + + scan == [P] [pop []] [[G] dupdip [scan] dip swons] ifte + +On the recursive branch `[G] dupdip` doesn't cut it: + + [1 2 3] [G] dupdip [scan] dip swons + [1 2 3] G [1 2 3] [scan] dip swons + +### Use `first` +At this point, we want the copy of `[1 2 3]` to just be `1`, so we use `first`. + + scan == [P] [pop []] [[G] dupdip first] [dip swons] genrec + + [1 2 3] [G] dupdip first [scan] dip swons + [1 2 3] G [1 2 3] first [scan] dip swons + [1 2 3] G 1 [scan] dip swons + +### `G` applies `⨁` +Now what does `G` have to do? Just apply `⨁` to the first two terms in the list. + + [1 2 3] G + [1 2 3] [⨁] infra + [1 2 3] [+] infra + [3 3] + +### Predicate `P` +Which tells us that the predicate `[P]` must guard against lists with less that two items in them: + + P == size 1 <= + +Let's see what we've got so far: + + scan == [P ] [pop []] [[G] dupdip first] [dip swons] genrec + scan == [size 1 <=] [pop []] [[[F] infra] dupdip first] [dip swons] genrec + +### Handling the Last Term +This works to a point, but it throws away the last term: + + +```python +J('[1 2 3] [size 1 <=] [pop []] [[[+] infra] dupdip first] [dip swons] genrec') +``` + + [1 3] + + +Hmm... Let's take out the `pop` for a sec... + + +```python +J('[1 2 3] [size 1 <=] [[]] [[[+] infra] dupdip first] [dip swons] genrec') +``` + + [6] [1 3] + + +That leaves the last item in our list, then it puts an empty list on the stack and `swons`'s the new terms onto that. If we leave out that empty list, they will be `swons`'d onto that list that already has the last item. + + +```python +J('[1 2 3] [size 1 <=] [] [[[+] infra] dupdip first] [dip swons] genrec') +``` + + [1 3 6] + + +### Parameterize `⨁` +So we have: + + [⨁] scan == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec + +Trivially: + + == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec + == [[[⨁] infra] dupdip first] [size 1 <=] [] roll< [dip swons] genrec + == [[⨁] infra] [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec + == [⨁] [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec + +And so: + + scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec + + +```python +define('scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec') +``` + + +```python +J('[1 2 3 4] [+] scan') +``` + + [1 3 6 10] + + + +```python +J('[1 2 3 4] [*] scan') +``` + + [1 2 6 24] + + + +```python +J('[1 2 3 4 5 6 7] [neg +] scan') +``` + + [1 1 2 2 3 3 4] + + +## Problem 2. +> Define a line to be a sequence of characters not containing the newline character. It is easy to define a function `Unlines` that converts a non-empty sequence of lines into a sequence of characters by inserting newline characters between every two lines. +> +> Since `Unlines` is injective, the function `Lines`, which converts a sequence of characters into a sequence of lines by splitting on newline characters, can be specified as the inverse of `Unlines`. +> +> The problem, just as in Problem 1. is to find a definition by reduction of the function `Lines`. + + + Unlines = uncons ['\n' swap + +] step + + + +```python +J('["hello" "world"] uncons ["\n" swap + +] step') +``` + + 'hello\nworld' + + +Again ignoring the actual task let's just derive `Lines`: + + "abc\nefg\nhij" Lines + --------------------------- + ["abc" "efg" "hij"] + +Instead of `P == [size 1 <=]` we want `["\n" in]`, and for the base-case of a string with no newlines in it we want to use `unit`: + + Lines == ["\n" in] [unit] [R0] [dip swons] genrec + Lines == ["\n" in] [unit] [R0 [Lines] dip swons] ifte + +Derive `R0`: + + "a \n b" R0 [Lines] dip swons + "a \n b" split-at-newline swap [Lines] dip swons + "a " " b" swap [Lines] dip swons + " b" "a " [Lines] dip swons + " b" Lines "a " swons + [" b"] "a " swons + ["a " " b"] + +So: + + R0 == split-at-newline swap + + Lines == ["\n" in] [unit] [split-at-newline swap] [dip swons] genrec + +## Missing the Point? +This is all good and well, but in the paper many interesting laws and properties are discussed. Am I missing the point? + + 0 [a b c d] [F] step == 0 [a b] [F] step 0 [c d] [F] step concat + +For associative function `F` and a "unit" element for that function, here represented by `0`. + +For functions that don't have a "unit" we can fake it (the example is given of infinity for the `min(a, b)` function.) We can also use: + + safe_step == [size 1 <=] [] [uncons [F] step] ifte + +Or: + + safe_step == [pop size 1 <=] [pop] [[uncons] dip step] ifte + + [a b c] [F] safe_step + --------------------------- + a [b c] [F] step + +To limit `F` to working on pairs of terms from its domain. + + diff --git a/docs/fun_with_scan.rst b/docs/fun_with_scan.rst new file mode 100644 index 0000000..ff54dc4 --- /dev/null +++ b/docs/fun_with_scan.rst @@ -0,0 +1,334 @@ + +.. code:: ipython2 + + from notebook_preamble import D, DefinitionWrapper, J, V, define + +On "Two Exercises Found in a Book on Algorithmics" +================================================== + +Bird & Meertens + +Define ``scan`` in terms of a reduction. +---------------------------------------- + + Problem I. The reduction operator ``/`` of APL takes some binary + operator ``⨁`` on its left and a vector ``x`` of values on its + right. The meaning of ``⨁/x`` for ``x = [a b ... z]`` is the value + ``a⨁b⨁...⨁z``. For this to be well-defined in the absence of + brackets, the operation ``⨁`` has to be associative. Now there is + another operator ``\`` of APL called ``scan``. Its effect is closely + related to reduction in that we have: + +:: + + ⨁\x = [a a⨁b a⨁b⨁c ... a⨁b⨁...⨁z] + + The problem is to find some definition of ``scan`` as a reduction. + In other words, we have to find some function ``f`` and an operator + ``⨂`` so that + +:: + + ⨂\x = f(a)⨂f(b)⨂...⨂f(z) + +Designing the Recursive Function +-------------------------------- + +Ignoring the exact requirements (finding ``f`` and ``⨂``) can we +implement ``scan`` as a hylomorphism in Joy? + +Looking at the forms of hylomorphism, ``H3`` is the one to use: + +``H3`` +~~~~~~ + +If the combiner and the generator both need to work on the current value +then ``dup`` must be used, and the generator must produce one item +instead of two (the b is instead the duplicate of a.) + +:: + + H3 == [P] [pop c] [[G] dupdip] [dip F] genrec + + ... a [G] dupdip [H3] dip F + ... a G a [H3] dip F + ... a′ a [H3] dip F + ... a′ H3 a F + ... a′ [G] dupdip [H3] dip F a F + ... a′ G a′ [H3] dip F a F + ... a″ a′ [H3] dip F a F + ... a″ H3 a′ F a F + ... a″ [G] dupdip [H3] dip F a′ F a F + ... a″ G a″ [H3] dip F a′ F a F + ... a‴ a″ [H3] dip F a′ F a F + ... a‴ H3 a″ F a′ F a F + ... a‴ pop c a″ F a′ F a F + ... c a″ F a′ F a F + ... d a′ F a F + ... d′ a F + ... d″ + +Initial Definition +~~~~~~~~~~~~~~~~~~ + +We're building a list of values so this is an "anamorphism". (An +anamorphism uses ``[]`` for ``c`` and ``swons`` for ``F``.) + +:: + + scan == [P] [pop []] [[G] dupdip] [dip swons] genrec + +Convert to ``ifte``: + +:: + + scan == [P] [pop []] [[G] dupdip [scan] dip swons] ifte + +On the recursive branch ``[G] dupdip`` doesn't cut it: + +:: + + [1 2 3] [G] dupdip [scan] dip swons + [1 2 3] G [1 2 3] [scan] dip swons + +Use ``first`` +~~~~~~~~~~~~~ + +At this point, we want the copy of ``[1 2 3]`` to just be ``1``, so we +use ``first``. + +:: + + scan == [P] [pop []] [[G] dupdip first] [dip swons] genrec + + [1 2 3] [G] dupdip first [scan] dip swons + [1 2 3] G [1 2 3] first [scan] dip swons + [1 2 3] G 1 [scan] dip swons + +``G`` applies ``⨁`` +~~~~~~~~~~~~~~~~~~~ + +Now what does ``G`` have to do? Just apply ``⨁`` to the first two terms +in the list. + +:: + + [1 2 3] G + [1 2 3] [⨁] infra + [1 2 3] [+] infra + [3 3] + +Predicate ``P`` +~~~~~~~~~~~~~~~ + +Which tells us that the predicate ``[P]`` must guard against lists with +less that two items in them: + +:: + + P == size 1 <= + +Let's see what we've got so far: + +:: + + scan == [P ] [pop []] [[G] dupdip first] [dip swons] genrec + scan == [size 1 <=] [pop []] [[[F] infra] dupdip first] [dip swons] genrec + +Handling the Last Term +~~~~~~~~~~~~~~~~~~~~~~ + +This works to a point, but it throws away the last term: + +.. code:: ipython2 + + J('[1 2 3] [size 1 <=] [pop []] [[[+] infra] dupdip first] [dip swons] genrec') + + +.. parsed-literal:: + + [1 3] + + +Hmm... Let's take out the ``pop`` for a sec... + +.. code:: ipython2 + + J('[1 2 3] [size 1 <=] [[]] [[[+] infra] dupdip first] [dip swons] genrec') + + +.. parsed-literal:: + + [6] [1 3] + + +That leaves the last item in our list, then it puts an empty list on the +stack and ``swons``'s the new terms onto that. If we leave out that +empty list, they will be ``swons``'d onto that list that already has the +last item. + +.. code:: ipython2 + + J('[1 2 3] [size 1 <=] [] [[[+] infra] dupdip first] [dip swons] genrec') + + +.. parsed-literal:: + + [1 3 6] + + +Parameterize ``⨁`` +~~~~~~~~~~~~~~~~~~ + +So we have: + +:: + + [⨁] scan == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec + +Trivially: + +:: + + == [size 1 <=] [] [[[⨁] infra] dupdip first] [dip swons] genrec + == [[[⨁] infra] dupdip first] [size 1 <=] [] roll< [dip swons] genrec + == [[⨁] infra] [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec + == [⨁] [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec + +And so: + +:: + + scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec + +.. code:: ipython2 + + define('scan == [infra] cons [dupdip first] cons [size 1 <=] [] roll< [dip swons] genrec') + +.. code:: ipython2 + + J('[1 2 3 4] [+] scan') + + +.. parsed-literal:: + + [1 3 6 10] + + +.. code:: ipython2 + + J('[1 2 3 4] [*] scan') + + +.. parsed-literal:: + + [1 2 6 24] + + +.. code:: ipython2 + + J('[1 2 3 4 5 6 7] [neg +] scan') + + +.. parsed-literal:: + + [1 1 2 2 3 3 4] + + +Problem 2. +---------- + + Define a line to be a sequence of characters not containing the + newline character. It is easy to define a function ``Unlines`` that + converts a non-empty sequence of lines into a sequence of characters + by inserting newline characters between every two lines. + + Since ``Unlines`` is injective, the function ``Lines``, which + converts a sequence of characters into a sequence of lines by + splitting on newline characters, can be specified as the inverse of + ``Unlines``. + + The problem, just as in Problem 1. is to find a definition by + reduction of the function ``Lines``. + +:: + + Unlines = uncons ['\n' swap + +] step + +.. code:: ipython2 + + J('["hello" "world"] uncons ["\n" swap + +] step') + + +.. parsed-literal:: + + 'hello\nworld' + + +Again ignoring the actual task let's just derive ``Lines``: + +:: + + "abc\nefg\nhij" Lines + --------------------------- + ["abc" "efg" "hij"] + +Instead of ``P == [size 1 <=]`` we want ``["\n" in]``, and for the +base-case of a string with no newlines in it we want to use ``unit``: + +:: + + Lines == ["\n" in] [unit] [R0] [dip swons] genrec + Lines == ["\n" in] [unit] [R0 [Lines] dip swons] ifte + +Derive ``R0``: + +:: + + "a \n b" R0 [Lines] dip swons + "a \n b" split-at-newline swap [Lines] dip swons + "a " " b" swap [Lines] dip swons + " b" "a " [Lines] dip swons + " b" Lines "a " swons + [" b"] "a " swons + ["a " " b"] + +So: + +:: + + R0 == split-at-newline swap + + Lines == ["\n" in] [unit] [split-at-newline swap] [dip swons] genrec + +Missing the Point? +------------------ + +This is all good and well, but in the paper many interesting laws and +properties are discussed. Am I missing the point? + +:: + + 0 [a b c d] [F] step == 0 [a b] [F] step 0 [c d] [F] step concat + +For associative function ``F`` and a "unit" element for that function, +here represented by ``0``. + +For functions that don't have a "unit" we can fake it (the example is +given of infinity for the ``min(a, b)`` function.) We can also use: + +:: + + safe_step == [size 1 <=] [] [uncons [F] step] ifte + +Or: + +:: + + safe_step == [pop size 1 <=] [pop] [[uncons] dip step] ifte + + [a b c] [F] safe_step + --------------------------- + a [b c] [F] step + +To limit ``F`` to working on pairs of terms from its domain. diff --git a/docs/notebook_preamble.pyc b/docs/notebook_preamble.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a6a4366acf9fa8e8418bda761a1aec591e134be GIT binary patch literal 1158 zcma)5%Wl(95Ir|`lR9ZhKvgBWVZp{5Dn0;JNUhYuqN-d(kwJ)d3AwQ%QK$Wy)$QK?0w%He*1dzHpQ>U?7`@93UQaRv*PW$^dcD>1!y4C`X7#Ft(2s5N{wGBHo1MOSz95 zCb{=p9jH1+wvHPHPml#T4M!|3|G_8hQE~y+VxMu;*&2tFtDV&u!Bk8`IUgB)y!vdfGasc;7iT=y=s6sXdXjwn-&{ z&>Cr<)IK6}d<~YslDvnNC(V4gGB`Y93s1G0GOe+_|Xr*%DpGx0RVWlhJvxmjb zt&*1>E?78cd{%`p#;}tT=tDfd$cC7>4O@ptOP|215#Idc6PN<#SPOZQzn~l{&ORjt z>qPpH+mE)1bXcNy|l4aA(o-4Wojflj2m#PHOvx0-llD(&tTN3XV z9?DMYT02>?88 + + +with_sympy + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+

Symbolic Evaluation with SymPy

+
+
+
+
+
+
In [1]:
+
+
+
import sympy
+
+from joy.joy import run
+from joy.library import UnaryBuiltinWrapper
+from joy.utils.pretty_print import TracePrinter
+from joy.utils.stack import list_to_stack
+
+from notebook_preamble import D, J, V, define
+
+ +
+
+
+ +
+
+
+
In [2]:
+
+
+
sympy.init_printing()
+
+ +
+
+
+ +
+
+
+
+
+

SypPy Symbols

The SymPy package provides a powerful and elegant "thunk" object that can take the place of a numeric value in calculations and "record" the operations performed on it.

+

We can create some of these objects and put them on the Joy stack:

+ +
+
+
+
+
+
In [3]:
+
+
+
stack = list_to_stack(sympy.symbols('c a b'))
+
+ +
+
+
+ +
+
+
+
+
+

If we evaluate the quadratic program

+ +
over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
+
+
+

The SypPy Symbols will become the symbolic expression of the math operations. Unfortunately, the library sqrt function doesn't work with the SymPy objects:

+ +
+
+
+
+
+
In [4]:
+
+
+
viewer = TracePrinter()
+try:
+    run('over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2', stack, D, viewer.viewer)
+except Exception, e:
+    print e
+viewer.print_()
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
can't convert expression to float
+                                            b a c . over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
+                                          b a c a . [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
+b a c a [[[neg] dupdip sqr 4] dipd * * - sqrt pm] . dip 2 * [/] cons app2
+                                            b a c . [[neg] dupdip sqr 4] dipd * * - sqrt pm a 2 * [/] cons app2
+                       b a c [[neg] dupdip sqr 4] . dipd * * - sqrt pm a 2 * [/] cons app2
+                                                b . [neg] dupdip sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
+                                          b [neg] . dupdip sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
+                                                b . neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
+                                               -b . b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
+                                             -b b . sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
+                                             -b b . dup mul 4 a c * * - sqrt pm a 2 * [/] cons app2
+                                           -b b b . mul 4 a c * * - sqrt pm a 2 * [/] cons app2
+                                          -b b**2 . 4 a c * * - sqrt pm a 2 * [/] cons app2
+                                        -b b**2 4 . a c * * - sqrt pm a 2 * [/] cons app2
+                                      -b b**2 4 a . c * * - sqrt pm a 2 * [/] cons app2
+                                    -b b**2 4 a c . * * - sqrt pm a 2 * [/] cons app2
+                                    -b b**2 4 a*c . * - sqrt pm a 2 * [/] cons app2
+                                    -b b**2 4*a*c . - sqrt pm a 2 * [/] cons app2
+                                 -b -4*a*c + b**2 . sqrt pm a 2 * [/] cons app2
+
+
+
+ +
+
+ +
+
+
+
+
+

We can pick out that first symbolic expression obect from the Joy stack:

+ +
+
+
+
+
+
In [5]:
+
+
+
S, E = viewer.history[-1]
+
+ +
+
+
+ +
+
+
+
In [6]:
+
+
+
q = S[0]
+q
+
+ +
+
+
+ +
+
+ + +
+ +
Out[6]:
+ + + + +
+$$- 4 a c + b^{2}$$ +
+ +
+ +
+
+ +
+
+
+
+
+

The Python math.sqrt() function causes the "can't convert expression to float" exception but sympy.sqrt() does not:

+ +
+
+
+
+
+
In [7]:
+
+
+
sympy.sqrt(q)
+
+ +
+
+
+ +
+
+ + +
+ +
Out[7]:
+ + + + +
+$$\sqrt{- 4 a c + b^{2}}$$ +
+ +
+ +
+
+ +
+
+
+
+
+

Use sympy.sqrt

This is easy to fix.

+ +
+
+
+
+
+
In [8]:
+
+
+
D['sqrt'] = UnaryBuiltinWrapper(sympy.sqrt)
+
+ +
+
+
+ +
+
+
+
+
+

Now it works just fine.

+ +
+
+
+
+
+
In [9]:
+
+
+
(root1, (root2, _)) = run('over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2', stack, D)[0]
+
+ +
+
+
+ +
+
+
+
In [10]:
+
+
+
root1
+
+ +
+
+
+ +
+
+ + +
+ +
Out[10]:
+ + + + +
+$$\frac{1}{2 a} \left(- b - \sqrt{- 4 a c + b^{2}}\right)$$ +
+ +
+ +
+
+ +
+
+
+
In [11]:
+
+
+
root2
+
+ +
+
+
+ +
+
+ + +
+ +
Out[11]:
+ + + + +
+$$\frac{1}{2 a} \left(- b + \sqrt{- 4 a c + b^{2}}\right)$$ +
+ +
+ +
+
+ +
+
+
+
+
+

At some point I will probably make an optional library of Joy wrappers for SymPy functions, and either load it automatically if SymPy installation is available or have a CLI switch or something. There's a huge amount of incredibly useful stuff and I don't see why Joy shouldn't expose another interface for using it. (As an example, the symbolic expressions can be "lambdafied" into very fast versions, i.e. a function that takes a, b, and c and computes the value of the root using just low-level fast code, bypassing Joy and Python. Also, Numpy, &c.)

+ +
+
+
+
+
+
+
+

Partial Evaluation

Futamura projections

+

Starting with the example from Partial Computation of Programs +by Yoshihiko Futamura of a function to compute u to the kth power:

+ +
+
+
+
+
+
In [12]:
+
+
+
def F(u, k):
+    z = 1
+    while k != 0:
+        if odd(k):
+            z = z * u
+        k = k / 2
+        u = u * u
+    return z
+
+ +
+
+
+ +
+
+
+
+
+

Partial evaluation with k = 5:

+ +
+
+
+
+
+
In [13]:
+
+
+
def F5(u):
+    z = 1 * u
+    u = u * u
+    u = u * u
+    z = z * u
+    return z
+
+ +
+
+
+ +
+
+
+
+
+

Translate F(u, k) to Joy

+ +
u k 1                    # z = 1
+    [pop] [Fw] while     # the while statement
+    popopd               # discard u k, "return" z
+ +
+
+
+
+
+
+
+

What's Fw?

+ +
u k z [pop odd] [Ft] [] ifte   # the if statement
+      [2 //] dip               # k = k / 2  floordiv
+      [sqr] dipd               # u = u * u
+
+      [[sqr] dip 2 //] dip     # We can merge last two lines.
+ +
+
+
+
+
+
+
+

Helper function Ft (to compute z = z * u).

+ +
   u k z Ft
+---------------
+   u k u*z
+
+
+Ft == [over] dip *
+ +
+
+
+
+
+
+
+

Putting it together:

+ +
Ft == [over] dip *
+Fb == [[sqr] dip 2 //] dip
+Fw == [pop odd] [Ft] [] ifte Fb
+ F == 1 [pop] [Fw] while popopd
+ +
+
+
+
+
+
In [14]:
+
+
+
define('odd == 2 %')
+
+ +
+
+
+ +
+
+
+
In [15]:
+
+
+
define('Ft == [over] dip *')
+
+ +
+
+
+ +
+
+
+
In [16]:
+
+
+
define('Fb == [[sqr] dip 2 //] dip')
+
+ +
+
+
+ +
+
+
+
In [17]:
+
+
+
define('Fw == [pop odd] [Ft] [] ifte Fb')
+
+ +
+
+
+ +
+
+
+
In [18]:
+
+
+
define('F == 1 [pop] [Fw] while popopd')
+
+ +
+
+
+ +
+
+
+
+
+

Try it out:

+ +
+
+
+
+
+
In [19]:
+
+
+
J('2 5 F')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
32
+
+
+
+ +
+
+ +
+
+
+
+
+

In order to elide the tests let's define special versions of while and ifte:

+ +
+
+
+
+
+
In [20]:
+
+
+
from joy.joy import joy
+from joy.library import FunctionWrapper
+from joy.parser import Symbol
+from joy.utils.stack import concat
+
+
+S_while = Symbol('while')
+
+
+@FunctionWrapper
+def while_(S, expression, dictionary):
+    '''[if] [body] while'''
+    (body, (if_, stack)) = S
+    if joy(stack, if_, dictionary)[0][0]:
+        expression = concat(body, (if_, (body, (S_while, expression))))
+    return stack, expression, dictionary
+
+
+@FunctionWrapper
+def ifte(stack, expression, dictionary):
+    '''[if] [then] [else] ifte'''
+    (else_, (then, (if_, stack))) = stack
+    if_res = joy(stack, if_, dictionary)[0][0]
+    quote = then if if_res else else_
+    expression = concat(quote, expression)
+    return (stack, expression, dictionary)
+
+
+D['ifte'] = ifte
+D['while'] = while_
+
+ +
+
+
+ +
+
+
+
+
+

And with a SymPy symbol for the u argument:

+ +
+
+
+
+
+
In [21]:
+
+
+
stack = list_to_stack([5, sympy.symbols('u')])
+viewer = TracePrinter()
+try:
+    (result, _) = run('F', stack, D, viewer.viewer)[0]
+except Exception, e:
+    print e
+viewer.print_()
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
                         u 5 . F
+                         u 5 . 1 [pop] [Fw] while popopd
+                       u 5 1 . [pop] [Fw] while popopd
+                 u 5 1 [pop] . [Fw] while popopd
+            u 5 1 [pop] [Fw] . while popopd
+                       u 5 1 . Fw [pop] [Fw] while popopd
+                       u 5 1 . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd
+             u 5 1 [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd
+        u 5 1 [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd
+     u 5 1 [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd
+                       u 5 1 . Ft Fb [pop] [Fw] while popopd
+                       u 5 1 . [over] dip * Fb [pop] [Fw] while popopd
+                u 5 1 [over] . dip * Fb [pop] [Fw] while popopd
+                         u 5 . over 1 * Fb [pop] [Fw] while popopd
+                       u 5 u . 1 * Fb [pop] [Fw] while popopd
+                     u 5 u 1 . * Fb [pop] [Fw] while popopd
+                       u 5 u . Fb [pop] [Fw] while popopd
+                       u 5 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd
+      u 5 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd
+                         u 5 . [sqr] dip 2 // u [pop] [Fw] while popopd
+                   u 5 [sqr] . dip 2 // u [pop] [Fw] while popopd
+                           u . sqr 5 2 // u [pop] [Fw] while popopd
+                           u . dup mul 5 2 // u [pop] [Fw] while popopd
+                         u u . mul 5 2 // u [pop] [Fw] while popopd
+                        u**2 . 5 2 // u [pop] [Fw] while popopd
+                      u**2 5 . 2 // u [pop] [Fw] while popopd
+                    u**2 5 2 . // u [pop] [Fw] while popopd
+                      u**2 2 . u [pop] [Fw] while popopd
+                    u**2 2 u . [pop] [Fw] while popopd
+              u**2 2 u [pop] . [Fw] while popopd
+         u**2 2 u [pop] [Fw] . while popopd
+                    u**2 2 u . Fw [pop] [Fw] while popopd
+                    u**2 2 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd
+          u**2 2 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd
+     u**2 2 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd
+  u**2 2 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd
+                    u**2 2 u . Fb [pop] [Fw] while popopd
+                    u**2 2 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd
+   u**2 2 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd
+                      u**2 2 . [sqr] dip 2 // u [pop] [Fw] while popopd
+                u**2 2 [sqr] . dip 2 // u [pop] [Fw] while popopd
+                        u**2 . sqr 2 2 // u [pop] [Fw] while popopd
+                        u**2 . dup mul 2 2 // u [pop] [Fw] while popopd
+                   u**2 u**2 . mul 2 2 // u [pop] [Fw] while popopd
+                        u**4 . 2 2 // u [pop] [Fw] while popopd
+                      u**4 2 . 2 // u [pop] [Fw] while popopd
+                    u**4 2 2 . // u [pop] [Fw] while popopd
+                      u**4 1 . u [pop] [Fw] while popopd
+                    u**4 1 u . [pop] [Fw] while popopd
+              u**4 1 u [pop] . [Fw] while popopd
+         u**4 1 u [pop] [Fw] . while popopd
+                    u**4 1 u . Fw [pop] [Fw] while popopd
+                    u**4 1 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd
+          u**4 1 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd
+     u**4 1 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd
+  u**4 1 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd
+                    u**4 1 u . Ft Fb [pop] [Fw] while popopd
+                    u**4 1 u . [over] dip * Fb [pop] [Fw] while popopd
+             u**4 1 u [over] . dip * Fb [pop] [Fw] while popopd
+                      u**4 1 . over u * Fb [pop] [Fw] while popopd
+                 u**4 1 u**4 . u * Fb [pop] [Fw] while popopd
+               u**4 1 u**4 u . * Fb [pop] [Fw] while popopd
+                 u**4 1 u**5 . Fb [pop] [Fw] while popopd
+                 u**4 1 u**5 . [[sqr] dip 2 //] dip [pop] [Fw] while popopd
+u**4 1 u**5 [[sqr] dip 2 //] . dip [pop] [Fw] while popopd
+                      u**4 1 . [sqr] dip 2 // u**5 [pop] [Fw] while popopd
+                u**4 1 [sqr] . dip 2 // u**5 [pop] [Fw] while popopd
+                        u**4 . sqr 1 2 // u**5 [pop] [Fw] while popopd
+                        u**4 . dup mul 1 2 // u**5 [pop] [Fw] while popopd
+                   u**4 u**4 . mul 1 2 // u**5 [pop] [Fw] while popopd
+                        u**8 . 1 2 // u**5 [pop] [Fw] while popopd
+                      u**8 1 . 2 // u**5 [pop] [Fw] while popopd
+                    u**8 1 2 . // u**5 [pop] [Fw] while popopd
+                      u**8 0 . u**5 [pop] [Fw] while popopd
+                 u**8 0 u**5 . [pop] [Fw] while popopd
+           u**8 0 u**5 [pop] . [Fw] while popopd
+      u**8 0 u**5 [pop] [Fw] . while popopd
+                 u**8 0 u**5 . popopd
+                        u**5 . 
+
+
+
+ +
+
+ +
+
+
+
In [22]:
+
+
+
result
+
+ +
+
+
+ +
+
+ + +
+ +
Out[22]:
+ + + + +
+$$u^{5}$$ +
+ +
+ +
+
+ +
+
+
+
+
+

Let's try partial evaluation by hand and use a "stronger" thunk.

+

Caret underscoring indicates terms that form thunks. When an arg is unavailable for a computation we just postpone it until the arg becomes available and in the meantime treat the pending computation as one unit.

+ +
                         u 5 . F
+                         u 5 . 1 [pop] [Fw] while popopd
+                       u 5 1 . [pop] [Fw] while popopd
+                 u 5 1 [pop] . [Fw] while popopd
+            u 5 1 [pop] [Fw] . while popopd
+                       u 5 1 . Fw [pop] [Fw] while popopd
+                       u 5 1 . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd
+             u 5 1 [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd
+        u 5 1 [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd
+     u 5 1 [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd
+                       u 5 1 . Ft Fb [pop] [Fw] while popopd
+                       u 5 1 . [over] dip * Fb [pop] [Fw] while popopd
+                u 5 1 [over] . dip * Fb [pop] [Fw] while popopd
+                         u 5 . over 1 * Fb [pop] [Fw] while popopd
+                       u 5 u . 1 * Fb [pop] [Fw] while popopd
+                     u 5 u 1 . * Fb [pop] [Fw] while popopd
+                       u 5 u . Fb [pop] [Fw] while popopd
+                       u 5 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd
+      u 5 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd
+                         u 5 . [sqr] dip 2 // u [pop] [Fw] while popopd
+                   u 5 [sqr] . dip 2 // u [pop] [Fw] while popopd
+                           u . sqr 5 2 // u [pop] [Fw] while popopd
+                           u . dup mul 5 2 // u [pop] [Fw] while popopd
+                     u dup * . 5 2 // u [pop] [Fw] while popopd
+                     ^^^^^^^
+ +
+
+
+
+
+
+
+ +
         u dup * 2 u [pop] [Fw] . while popopd
+                    u dup * 2 u . Fw [pop] [Fw] while popopd
+                    u dup * 2 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd
+          u dup * 2 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd
+     u dup * 2 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd
+  u dup * 2 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd
+                    u dup * 2 u . Fb [pop] [Fw] while popopd
+                    u dup * 2 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd
+   u dup * 2 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd
+                      u dup * 2 . [sqr] dip 2 // u [pop] [Fw] while popopd
+                u dup * 2 [sqr] . dip 2 // u [pop] [Fw] while popopd
+                        u dup * . sqr 2 2 // u [pop] [Fw] while popopd
+                        u dup * . dup mul 2 2 // u [pop] [Fw] while popopd
+                  u dup * dup * . 2 2 // u [pop] [Fw] while popopd
+                  ^^^^^^^^^^^^^
+ +
+
+
+
+
+
+
+

w/ K == u dup * dup *

+ +
             K 1 u [pop] [Fw] . while popopd
+                        K 1 u . Fw [pop] [Fw] while popopd
+                        K 1 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd
+              K 1 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd
+         K 1 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd
+      K 1 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd
+                        K 1 u . Ft Fb [pop] [Fw] while popopd
+                        K 1 u . [over] dip * Fb [pop] [Fw] while popopd
+                 K 1 u [over] . dip * Fb [pop] [Fw] while popopd
+                          K 1 . over u * Fb [pop] [Fw] while popopd
+                        K 1 K . u * Fb [pop] [Fw] while popopd
+                      K 1 K u . * Fb [pop] [Fw] while popopd
+                    K 1 K u * . Fb [pop] [Fw] while popopd
+                        ^^^^^
+ +
+
+
+
+
+
+
+

w/ L == K u *

+ +
                     K 1 L . Fb [pop] [Fw] while popopd
+                     K 1 L . [[sqr] dip 2 //] dip [pop] [Fw] while popopd
+    K 1 L [[sqr] dip 2 //] . dip [pop] [Fw] while popopd
+                       K 1 . [sqr] dip 2 // L [pop] [Fw] while popopd
+                 K 1 [sqr] . dip 2 // L [pop] [Fw] while popopd
+                         K . sqr 1 2 // L [pop] [Fw] while popopd
+                         K . dup mul 1 2 // L [pop] [Fw] while popopd
+                       K K . mul 1 2 // L [pop] [Fw] while popopd
+                     K K * . 1 2 // L [pop] [Fw] while popopd
+                     ^^^^^
+                     K K * . 1 2 // L [pop] [Fw] while popopd
+                   K K * 1 . 2 // L [pop] [Fw] while popopd
+                 K K * 1 2 . // L [pop] [Fw] while popopd
+                   K K * 0 . L [pop] [Fw] while popopd
+                 K K * 0 L . [pop] [Fw] while popopd
+           K K * 0 L [pop] . [Fw] while popopd
+      K K * 0 L [pop] [Fw] . while popopd
+      ^^^^^
+                 K K * 0 L . popopd
+                         L . 
+ +
+
+
+
+
+
+
+

So:

+ +
K == u dup * dup *
+L == K u *
+
+
+

Our result "thunk" would be:

+ +
u dup * dup * u *
+
+
+

Mechanically, you could do:

+ +
u      dup * dup *  u   *
+u u   [dup * dup *] dip *
+u dup [dup * dup *] dip *
+
+
+F5 == dup [dup * dup *] dip *
+
+
+

But we can swap the two arguments to the final * to get all mentions of u to the left:

+ +
u u dup * dup * *
+
+
+
+

Then de-duplicate "u":

+ +
u dup dup * dup * *
+
+
+
+

To arrive at a startlingly elegant form for F5:

+ +
F5 == dup dup * dup * *
+ +
+
+
+
+
+
In [23]:
+
+
+
stack = list_to_stack([sympy.symbols('u')])
+viewer = TracePrinter()
+try:
+    (result, _) = run('dup dup * dup * *', stack, D, viewer.viewer)[0]
+except Exception, e:
+    print e
+viewer.print_()
+result
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
          u . dup dup * dup * *
+        u u . dup * dup * *
+      u u u . * dup * *
+     u u**2 . dup * *
+u u**2 u**2 . * *
+     u u**4 . *
+       u**5 . 
+
+
+
+ +
+ +
Out[23]:
+ + + + +
+$$u^{5}$$ +
+ +
+ +
+
+ +
+
+
+
+
+

I'm not sure how to implement these kinds of thunks. I think you have to have support in the interpreter, or you have to modify all of the functions like dup to check for thunks in their inputs.

+ +
+
+
+
+
+
+
+

Working on the compiler, from this:

+ +
dup dup * dup * *
+
+
+

We can already generate:

+ +
---------------------------------
+(a0, stack) = stack
+a1 = mul(a0, a0)
+a2 = mul(a1, a1)
+a3 = mul(a2, a0)
+stack = (a3, stack)
+---------------------------------
+
+
+

This is pretty old stuff... (E.g. from 1999, M. Anton Ertl Compilation of Stack-Based Languages he goes a lot further for Forth.)

+ +
+
+
+
+
+
+
+

"A Transformation Based Approach to Semantics-Directed Code Generation"

by Arthur Nunes-Harwitt

+

https://dl.acm.org/citation.cfm?doid=2635648.2635652

+ +
+
+
+
+
+
In [24]:
+
+
+
def m(x, y): return x * y
+
+print m(2, 3)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
6
+
+
+
+ +
+
+ +
+
+
+
In [25]:
+
+
+
def m(x): return lambda y: x * y
+
+print m(2)(3)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
6
+
+
+
+ +
+
+ +
+
+
+
In [26]:
+
+
+
def m(x): return "lambda y: %(x)s * y" % locals()
+
+print m(2)
+print eval(m(2))(3)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
lambda y: 2 * y
+6
+
+
+
+ +
+
+ +
+
+
+
+
+

In Joy:

+ +
m == [*] cons
+
+3 2 m i
+3 2 [*] cons i
+3 [2 *] i
+3 2 *
+6
+ +
+
+
+
+
+
In [27]:
+
+
+
def p(n, b): # original
+    return 1 if n == 0 else b * p(n - 1, b)
+
+
+def p(n): # curried
+    return lambda b: 1 if n == 0 else b * p(n - 1, b)
+
+
+def p(n): # quoted
+    return "lambda b: 1 if %(n)s == 0 else b * p(%(n)s - 1, b)"  % locals()
+
+
+print p(3)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
lambda b: 1 if 3 == 0 else b * p(3 - 1, b)
+
+
+
+ +
+
+ +
+
+
+
+
+

Original

+ +
p == [0 =] [popop 1] [-- over] [dip *] genrec
+
+b n p
+b n [0 =] [popop 1] [-- over [p] dip *]
+
+b n -- over [p] dip *
+b n-1  over [p] dip *
+b n-1 b [p] dip *
+b n-1 p b *
+ +
+
+
+
+
+
+
+

curried, quoted

+ +
                    n p
+---------------------------------------------
+   [[n 0 =] [pop 1] [dup n --] [*] genrec]
+ +
+
+
+
+
+
In [28]:
+
+
+
def p(n): # lambda lowered
+    return (
+        lambda b: 1
+        if n == 0 else
+        lambda b: b * p(n - 1, b)
+        )
+
+
+def p(n): # lambda lowered quoted
+    return (
+        "lambda b: 1"
+        if n == 0 else
+        "lambda b: b * p(%(n)s - 1, b)"  % locals()
+        )
+
+print p(3)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
lambda b: b * p(3 - 1, b)
+
+
+
+ +
+
+ +
+
+
+
+
+ +
p == [0 =] [[pop 1]] [ [-- [dup] dip p *] cons ]ifte
+
+
+3 p
+3 [-- [dup] dip p *] cons
+[3 -- [dup] dip p *]
+ +
+
+
+
+
+
In [29]:
+
+
+
def p(n): # expression lifted
+    if n == 0:
+        return lambda b: 1
+    f = p(n - 1)
+    return lambda b: b * f(b)
+
+
+print p(3)(2)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
8
+
+
+
+ +
+
+ +
+
+
+
In [30]:
+
+
+
def p(n): # quoted
+    if n == 0:
+        return "lambda b: 1"
+    f = p(n - 1)
+    return "lambda b: b * (%(f)s)(b)"  % locals()
+
+print p(3)
+print eval(p(3))(2)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
lambda b: b * (lambda b: b * (lambda b: b * (lambda b: 1)(b))(b))(b)
+8
+
+
+
+ +
+
+ +
+
+
+
+
+ +
p == [0 =] [pop [pop 1]] [-- p [dupdip *] cons] ifte
+
+
+3 p
+3 -- p [dupdip *] cons
+2    p [dupdip *] cons
+2 -- p [dupdip *] cons [dupdip *] cons
+1    p [dupdip *] cons [dupdip *] cons
+1 -- p [dupdip *] cons [dupdip *] cons [dupdip *] cons
+0    p [dupdip *] cons [dupdip *] cons [dupdip *] cons
+0 pop [pop 1] [dupdip *] cons [dupdip *] cons [dupdip *] cons
+[pop 1] [dupdip *] cons [dupdip *] cons [dupdip *] cons
+...
+[[[[pop 1] dupdip *] dupdip *] dupdip *]
+
+
+2 [[[[pop 1] dupdip *] dupdip *] dupdip *] i
+2  [[[pop 1] dupdip *] dupdip *] dupdip *
+2   [[pop 1] dupdip *] dupdip *  2 *
+2    [pop 1] dupdip *  2      *  2 *
+2     pop 1  2      *  2      *  2 *
+          1  2      *  2      *  2 *
+
+
+
+p == [0 =] [pop [pop 1]] [--  p    [dupdip *] cons] ifte
+p == [0 =] [pop [pop 1]] [-- [p] i [dupdip *] cons] ifte
+p == [0 =] [pop [pop 1]] [--]   [i [dupdip *] cons] genrec
+ +
+
+
+
+
+
In [31]:
+
+
+
define('p == [0 =] [pop [pop 1]] [--] [i [dupdip *] cons] genrec')
+
+ +
+
+
+ +
+
+
+
In [32]:
+
+
+
J('3 p')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[[[[pop 1] dupdip *] dupdip *] dupdip *]
+
+
+
+ +
+
+ +
+
+
+
In [33]:
+
+
+
V('2 [[[[pop 1] dupdip *] dupdip *] dupdip *] i')
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
                                           . 2 [[[[pop 1] dupdip *] dupdip *] dupdip *] i
+                                         2 . [[[[pop 1] dupdip *] dupdip *] dupdip *] i
+2 [[[[pop 1] dupdip *] dupdip *] dupdip *] . i
+                                         2 . [[[pop 1] dupdip *] dupdip *] dupdip *
+           2 [[[pop 1] dupdip *] dupdip *] . dupdip *
+                                         2 . [[pop 1] dupdip *] dupdip * 2 *
+                      2 [[pop 1] dupdip *] . dupdip * 2 *
+                                         2 . [pop 1] dupdip * 2 * 2 *
+                                 2 [pop 1] . dupdip * 2 * 2 *
+                                         2 . pop 1 2 * 2 * 2 *
+                                           . 1 2 * 2 * 2 *
+                                         1 . 2 * 2 * 2 *
+                                       1 2 . * 2 * 2 *
+                                         2 . 2 * 2 *
+                                       2 2 . * 2 *
+                                         4 . 2 *
+                                       4 2 . *
+                                         8 . 
+
+
+
+ +
+
+ +
+
+
+
In [ ]:
+
+
+
stack = list_to_stack([sympy.symbols('u')])
+(result, s) = run('p i', stack, D)[0]
+result
+
+ +
+
+
+ +
+
+
+
+
+

From this:

+ +
p == [0 =] [pop pop 1] [-- over] [dip *] genrec
+
+
+

To this:

+ +
p == [0 =] [pop [pop 1]] [--] [i [dupdip *] cons] genrec
+ +
+
+
+
+
+
+
+

Try it with F():

+
+
+
+
+
+
In [ ]:
+
+
+
def odd(n): return n % 2
+
+
+def F(u, k):
+    z = 1
+    while k != 0:
+        if odd(k):
+            z = z * u
+        k = k / 2
+        u = u * u
+    return z
+
+F(2, 5)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
def F(k):
+    def _F(u, k=k):
+        z = 1
+        while k != 0:
+            if odd(k):
+                z = z * u
+            k = k / 2
+            u = u * u
+        return z
+    return _F
+    
+F(5)(2)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
def F(k):
+    def _F(u, k=k):
+        if k == 0:
+            z = 1
+        else:
+            z = F(k / 2)(u)
+            z *= z
+            if odd(k):
+                z = z * u
+        return z
+    return _F
+    
+F(5)(2)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
def F(k):
+    if k == 0:
+        z = lambda u: 1
+    else:
+        f = F(k / 2)
+        def z(u):
+            uu = f(u)
+            uu *= uu
+            return uu * u if odd(k) else uu
+    return z
+    
+F(5)(2)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
def F(k):
+    if k == 0:
+        z = lambda u: 1
+    else:
+        f = F(k / 2)
+        if odd(k):
+            z = lambda u: (lambda fu, u: fu * fu * u)(f(u), u)
+        else:
+            z = lambda u: (lambda fu, u: fu * fu)(f(u), u)
+    return z
+    
+F(5)(2)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
def F(k):
+    if k == 0:
+        z = "lambda u: 1"
+    else:
+        f = F(k / 2)
+        if odd(k):
+            z = "lambda u: (lambda fu, u: fu * fu * u)((%(f)s)(u), u)" % locals()
+        else:
+            z = "lambda u: (lambda fu, u: fu * fu)((%(f)s)(u), u)" % locals()
+    return z
+    
+source = F(5)
+print source
+eval(source)(2)
+
+ +
+
+
+ +
+
+
+
+
+

Hmm...

+ +
+
+
+
+
+
In [ ]:
+
+
+
for n in range(4):
+    print F(n)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
def F(k):
+    if k == 0:
+        z = "lambda u: 1"
+    elif k == 1:
+        z = "lambda u: u"
+    else:
+        f = F(k / 2)
+        if odd(k):
+            z = "lambda u: (lambda fu, u: fu * fu * u)((%(f)s)(u), u)" % locals()
+        else:
+            z = "lambda u: (lambda fu, u: fu * fu)((%(f)s)(u), u)" % locals()
+    return z
+    
+source = F(5)
+print source
+eval(source)(2)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
for n in range(4):
+    print F(n)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
def F(k):
+    if k == 0:
+        z = "lambda u: 1"
+    elif k == 1:
+        z = "lambda u: u"
+    else:
+        m = k / 2
+        if odd(k):
+            if m == 0:
+                z = "lambda u: 1"
+            elif m == 1:
+                z = "lambda u: u * u * u"
+            else:
+                z = "lambda u: (lambda fu, u: fu * fu * u)((%s)(u), u)" % F(m)
+        else:
+            if m == 0:
+                z = "lambda u: 1"
+            elif m == 1:
+                z = "lambda u: u * u"
+            else:
+                z = "lambda u: (lambda u: u * u)((%s)(u))" % F(m)
+    return z
+    
+source = F(5)
+print source
+eval(source)(2)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
def F(k):
+    if k == 0:
+        z = "lambda u: 1"
+    elif k == 1:
+        z = "lambda u: u"
+    else:
+        m = k / 2
+        if m == 0:
+            z = "lambda u: 1"
+        
+        elif odd(k):
+            if m == 1:
+                z = "lambda u: u * u * u"
+            else:
+                z = "lambda u: (lambda fu, u: fu * fu * u)((%s)(u), u)" % F(m)
+        else:
+            if m == 1:
+                z = "lambda u: u * u"
+            else:
+                z = "lambda u: (lambda u: u * u)((%s)(u))" % F(m)
+    return z
+    
+source = F(5)
+print source
+eval(source)(2)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
for n in range(7):
+    source = F(n)
+    print n, '%2i' % eval(source)(2), source
+
+ +
+
+
+ +
+
+
+
+
+

So that gets pretty good, eh?

+

But looking back at the definition in Joy, it doesn't seem easy to directly apply this technique to Joy code:

+ +
Ft == [over] dip *
+Fb == [[sqr] dip 2 //] dip
+Fw == [pop odd] [Ft] [] ifte Fb
+ F == 1 [pop] [Fw] while popopd
+
+
+
+

But a direct translation of the Python code..?

+ +
F == [
+  [[0 =] [pop 1]]
+  [[1 =] []]
+  [_F.0]
+  ] cond
+
+_F.0 == dup 2 // [
+  [[0 =]     [pop 1]]
+  [[pop odd] _F.1]
+  [_F.2]
+  ] cond
+
+_F.1 == [1 =] [pop [dup dup * *]] [popd F [dupdip over * *] cons] ifte
+_F.2 == [1 =] [pop [dup *]]       [popd F [i dup *]         cons] ifte
+
+
+
+

Try it:

+ +
5 F
+5 [ [[0 =] [pop 1]] [[1 =] []] [_F.0] ] cond
+5 _F.0
+5 dup 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond
+5 5 2 //   [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond
+
+5 2 [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond
+5 2 _F.1
+5 2 [1 =] [popop [dup dup * *]] [popd F [dupdip over * *] cons] ifte
+5 2                              popd F [dupdip over * *] cons
+  2                                   F [dupdip over * *] cons
+
+2 F [dupdip over * *] cons
+
+2 F
+2 [ [[0 =] [pop 1]] [[1 =] []] [_F.0] ] cond
+2 _F.0
+2 dup 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond
+2 2 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond
+2 1 [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond
+2 1 _F.2
+2 1 [1 =] [popop [dup *]] [popd F [i dup *] cons] ifte
+2 1 popop [dup *]
+[dup *]
+
+
+2 F     [dupdip over * *] cons
+[dup *] [dupdip over * *] cons
+[[dup *] dupdip over * *]
+
+
+

And here it is in action:

+ +
2 [[dup *] dupdip over * *] i
+2  [dup *] dupdip over * *
+2   dup *  2      over * *
+2   2   *  2      over * *
+4          2      over * *
+4          2      4    * *
+4          8             *
+32
+
+
+
+
+

So, it works, but in this case the results of the partial evaluation are more elegant.

+ +
+
+
+
+
+
+
+

Try it with hylomorphism():

+
+
+
+
+
+
In [ ]:
+
+
+
def hylomorphism(c, F, P, G):
+    '''Return a hylomorphism function H.'''
+
+    def H(a):
+        if P(a):
+            result = c
+        else:
+            b, aa = G(a)
+            result = F(b, H(aa))
+        return result
+
+    return H
+
+ +
+
+
+ +
+
+
+
+
+

First, curry

With abuse of syntax:

+ +
+
+
+
+
+
In [ ]:
+
+
+
def hylomorphism(c):
+    return lambda F: lambda P: lambda G: lambda a: (
+        if P(a):
+            result = c
+        else:
+            b, aa = G(a)
+            result = F(b)(H(aa))
+        return result
+    )
+
+ +
+
+
+ +
+
+
+
+
+

lambda lowering

+
+
+
+
+
+
In [ ]:
+
+
+
def hylomorphism(c):
+    def r(a):
+        def rr(P):
+            if P(a):
+                return lambda F: lambda G: c
+            return lambda F: lambda G: (
+                    b, aa = G(a)
+                    return F(b)(H(aa))
+                )
+        return rr
+    return r
+
+ +
+
+
+ +
+
+
+
+
+

expression lifting

+
+
+
+
+
+
In [ ]:
+
+
+
def hylomorphism(c):
+    def r(a):
+        def rr(P):
+            def rrr(G):
+                if P(a):
+                    return lambda F: c
+                b, aa = G(a)
+                H = hylomorphism(c)(aa)(P)(G)
+                return lambda F: F(b)(H(F))
+            return rrr
+        return rr
+    return r
+
+ +
+
+
+ +
+
+
+
+
+

quoted

+
+
+
+
+
+
In [ ]:
+
+
+
def hylomorphism(c):
+    def r(a):
+        def rr(P):
+            def rrr(G):
+                if P(a):
+                    return "lambda F: %s" % (c,)
+                b, aa = G(a)
+                H = hylomorphism(c)(aa)(P)(G)
+                return "lambda F: F(%(b)s)((%(H)s)(F))" % locals()
+            return rrr
+        return rr
+    return r
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
hylomorphism(0)(3)(lambda n: n == 0)(lambda n: (n-1, n-1))
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
def F(a):
+    def _F(b):
+        print a, b
+        return a + b
+    return _F
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
F(2)(3)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
eval(hylomorphism(0)(5)(lambda n: n == 0)(lambda n: (n-1, n-1)))(F)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
eval(hylomorphism([])(5)(lambda n: n == 0)(lambda n: (n-1, n-1)))(lambda a: lambda b: [a] + b)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
hylomorphism(0)([1, 2, 3])(lambda n: not n)(lambda n: (n[0], n[1:]))
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
hylomorphism([])([1, 2, 3])(lambda n: not n)(lambda n: (n[1:], n[1:]))
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
eval(hylomorphism([])([1, 2, 3])(lambda n: not n)(lambda n: (n[1:], n[1:])))(lambda a: lambda b: [a] + b)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
def hylomorphism(c):
+    return lambda a: lambda P: (
+        if P(a):
+            result = lambda F: lambda G: c
+        else:
+            result = lambda F: lambda G: (
+                b, aa = G(a)
+                return F(b)(H(aa))
+            )
+        return result
+    )
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
def hylomorphism(c):
+    return lambda a: (
+        lambda F: lambda P: lambda G: c
+        if P(a) else
+        lambda F: lambda P: lambda G: (
+            b, aa = G(a)
+            return F(b)(H(aa))
+        )
+    )
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
def hylomorphism(c):
+    return lambda a: lambda G: (
+        lambda F: lambda P: c
+        if P(a) else
+        b, aa = G(a)
+        lambda F: lambda P: F(b)(H(aa))
+    )
+
+ +
+
+
+ +
+
+
+ + + + + + diff --git a/docs/with_sympy.ipynb b/docs/with_sympy.ipynb new file mode 100644 index 0000000..f5ea88d --- /dev/null +++ b/docs/with_sympy.ipynb @@ -0,0 +1,1873 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Symbolic Evaluation with SymPy" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "from joy.joy import run\n", + "from joy.library import UnaryBuiltinWrapper\n", + "from joy.utils.pretty_print import TracePrinter\n", + "from joy.utils.stack import list_to_stack\n", + "\n", + "from notebook_preamble import D, J, V, define" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "sympy.init_printing()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## [SypPy Symbols](http://docs.sympy.org/latest/modules/core.html#module-sympy.core.symbol)\n", + "\n", + "The SymPy package provides a powerful and elegant [\"thunk\"](https://en.wikipedia.org/wiki/Thunk) object that can take the place of a numeric value in calculations and \"record\" the operations performed on it.\n", + "\n", + "We can create some of these objects and put them on the Joy stack:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "stack = list_to_stack(sympy.symbols('c a b'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we evaluate the `quadratic` program\n", + "\n", + " over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2\n", + "\n", + "The [SypPy Symbols](http://docs.sympy.org/latest/modules/core.html#module-sympy.core.symbol) will become the symbolic expression of the math operations. Unfortunately, the library `sqrt` function doesn't work with the SymPy objects:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "can't convert expression to float\n", + " b a c . over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2\n", + " b a c a . [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2\n", + "b a c a [[[neg] dupdip sqr 4] dipd * * - sqrt pm] . dip 2 * [/] cons app2\n", + " b a c . [[neg] dupdip sqr 4] dipd * * - sqrt pm a 2 * [/] cons app2\n", + " b a c [[neg] dupdip sqr 4] . dipd * * - sqrt pm a 2 * [/] cons app2\n", + " b . [neg] dupdip sqr 4 a c * * - sqrt pm a 2 * [/] cons app2\n", + " b [neg] . dupdip sqr 4 a c * * - sqrt pm a 2 * [/] cons app2\n", + " b . neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2\n", + " -b . b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2\n", + " -b b . sqr 4 a c * * - sqrt pm a 2 * [/] cons app2\n", + " -b b . dup mul 4 a c * * - sqrt pm a 2 * [/] cons app2\n", + " -b b b . mul 4 a c * * - sqrt pm a 2 * [/] cons app2\n", + " -b b**2 . 4 a c * * - sqrt pm a 2 * [/] cons app2\n", + " -b b**2 4 . a c * * - sqrt pm a 2 * [/] cons app2\n", + " -b b**2 4 a . c * * - sqrt pm a 2 * [/] cons app2\n", + " -b b**2 4 a c . * * - sqrt pm a 2 * [/] cons app2\n", + " -b b**2 4 a*c . * - sqrt pm a 2 * [/] cons app2\n", + " -b b**2 4*a*c . - sqrt pm a 2 * [/] cons app2\n", + " -b -4*a*c + b**2 . sqrt pm a 2 * [/] cons app2\n" + ] + } + ], + "source": [ + "viewer = TracePrinter()\n", + "try:\n", + " run('over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2', stack, D, viewer.viewer)\n", + "except Exception, e:\n", + " print e\n", + "viewer.print_()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can pick out that first symbolic expression obect from the Joy stack:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "S, E = viewer.history[-1]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGUAAAAWCAYAAADZylKgAAAABHNCSVQICAgIfAhkiAAAA3NJREFUaIHt2GuIVVUUB/CfpWU1wlRYTSFDEUHQ5IxCURBMI9EDouhDH3rYNYSgQII+9ADh4oeIpJoi+iAUlUUFlZEETQ0ZvSw1TaJIU5kgMppeGFFWNn1Y+zKn47l35p6544jcP2wOZ+291v6fu/Z67EsbbbQB7sVm7MMo1uO8GWXUhiEsE47owTp8j5NmktSRhtsxhltL6nfgAK6Go1pEqh5uFmTHsHya95pJLErPT0vqzxO++Lk1dOpjAX7Fb458p2zDH5hdUv+lZOPoljEqwCwMYzdWO7ydUhH8+kvqH4u/8ElJ/dXYi7NrgulKXyswIIrZ75NYfwOex04RWb+I7mRZA51+ccK+xX7xYUO4tizpkujBHJG6evEKfhLf/ZbGXdVDWIol2FUTTodTzsUDeBTvTWL9PDyDs/A+HserOBNP4e4CnUFsEI4fxsPpfTEunhr9prE4PbvxoYi6J7EVl+EddBboPYabcCm+nE6Cs7EFO3BcklU1Tl8dOK1A3iWi5quc/P5k72WcUGCru0nOFVNLX2uS/o/oy809m+buy8mfEHeUAfHttdFRkkNDrBKt3UUZWVX5mrIDP2Te+5L9zTimHMWDUDE1p2xRvx2+MM29kJOP1RnVog1GGigUjecyuhfgHzyYs1nV2CknYiU+FrXkQG6PrZm1a5NsSR1bE2GkwbcUjacnsDdH1LMRxZ1Xd7KzrhmSeUO78WcT+t9l7KwVhXplE/rni2J4KjbhRdGr/y1qylJsz6y/XDhuQxN7ZDHo4Pzei2tEXRvJzX02gb0eEbHrxYHMo5ZKv2mKZYvQafKnbzCjt10cgv4Cm6vS+jvS+9z0vq3F3CvKp6/lSfeuOvOPpPmrmjFa9rKTx37RcRRhkagFH4gasTHJF4hIGcK7OZ1O4+1w7ZY8Kz1PmTrdlqHWeRX9Z9WF2/C1yAaHFaqKa8r8JN8pcnMNJwtHjYk0Njcz93mSX1+wzznK3YgrykfKpqS7x/87wQ68LerjFc0abVWklMGo6OEHxG14WJyuK0XN+Ff079kadw9eF7XnFnwhoqpXRF7XIeJO/HY9Ip0eL+rPa+KGfx1Ox5148xBymjSq6ndf80UvPyruJB+J4t5n/BKWxyV4w3hDsFekhxtL8qsoFykLk94anCEuvPvSGCphr4022mijjTZajv8AHtTyPwIcKPQAAAAASUVORK5CYII=\n", + "text/latex": [ + "$$- 4 a c + b^{2}$$" + ], + "text/plain": [ + " 2\n", + "-4⋅a⋅c + b " + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q = S[0]\n", + "q" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Python `math.sqrt()` function causes the \"can't convert expression to float\" exception but `sympy.sqrt()` does not:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAHoAAAAdCAYAAABlqHgGAAAABHNCSVQICAgIfAhkiAAABGhJREFUaIHt2muIVVUUwPGfpqU1ghVWFpMUJJVZPnpgUExK9AAthPrQQ8cQgoIIMnqANPghelBalIRgL3tCmSVBlmVU9lDTLHpoD6aIpKwMQ0pKpw/rDF6v597uOffMVeT84XDn7L3X2uucvfdaa+8zlJSU7D/0yyk3FFuKNKRk32Q6LtvbRpT0Pc9gyN42oqRx+ueQOTC5/izYlpI+JM9AT8TbBdtRsg/yMI7d20aUZCPPim7HD0UbUpKJ27AaW7EZS3FKPYGsAz0Wn+QyraRIOjAfZ4tQ+i+W47CiOrgDZxSlrARchx5c04SONuzA5FoNsq7o8VjThEFZuVq8hB7MbGG/rWRc8vtxEzqGiLH8vXlzOAaPFKGoQdrxh9jG7c8DvQ5/YUATOp5P9BxQq0Haiq61yieLoN8K+uEx/Ka1kysPnWIiduSQPQij8KmIs3m4F+eKk8odtRpVD+pkfJlSDpPwZk5jsnKDSDJmYFsD7a/A09goPMAWkZXOqCPTIVbCj9iOTViGS/ManYPRGCjc9hi8KCb3NrzufzJp3IdpYmy+qdewckBPFw9/gsjmKjlEzJa/G7G+SU7CXXgA7zTQfgiewPF4Fw9hMY7Do7glRWYeVojJtBz3J/fj7fnsfcn45HcEVgrPsBBrcT7eEh+Q0ngQV+E8fJGn85W4p6psquaywkYZIJK9DRiclHWpH6PbcFRK+XCxur+qKr8z0feCmMDVukZktLlTfte9IJH9VWxdK3kyqbs9RW6+2ENPFM/ee7Vl6fxm8aIrWYgjsijJyRzhOSZUlHXJn4xtwC8V92MT/avFeX0RdMo/0GvU3lqdldQ9m1LXU+PqytL5yEToxOS+P5Y0INddx4C066kq+TNFQlLtTbrUH+hDMRsfiti8o6qftRVtFyVlkxp4njS6ZXvGx+voGihyg27pGfeIRMdLOW3djbQONoqVMEW4vQn4qAFd38oWw3+qsmNR0vfsDDpOFUnLkViF58Re8h8Ro6dhfUX7C8RkWJGhj0rm2TNmjsElIk/orqqrd4o4WniVpdIz7t4Q8n1mKzNwt4jVvX+P6svOxMtrdJXMq5BbLyZXR4rOOUn765P7Qcn9uoJt75TPdc9M5G6qUT83qb84r2GV1Nqkv4xZIi6fjM+L6KwO20UekMY4EVvfE57mg6S8XazoZfb8bDrUrq1V74lT779NtSLXaITejDvtfHo4rsXXwmP1Gf3xs8hQ5/ZlRw3QJT1GD0vKN4p418vhYvB7hAsfVFH3WVJ+eUo/I9U5WapDp3wrelUi953ds/82vCFyjQtz2JNKrRW9U8SOWUV2VjCbxT5zosghlouVcJGIwTvF/rIyb7gVr4hYPl14qqEizrYn8q1ggIjR63CwiOVLxEnZVByNG/FaK4yZIhKXZs5gi6BL7ax7mNhvbhZ75vdFAjbWrsOHas7Bq3YlbZuEe7wyp32dsq/o0xKZBeIbwmKxL94qvFEWXU0zWHzgLikpKSkpKWk9/wEOzhV2ng+cdgAAAABJRU5ErkJggg==\n", + "text/latex": [ + "$$\\sqrt{- 4 a c + b^{2}}$$" + ], + "text/plain": [ + " _____________\n", + " ╱ 2 \n", + "╲╱ -4⋅a⋅c + b " + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sympy.sqrt(q)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use `sympy.sqrt`\n", + "This is easy to fix." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "D['sqrt'] = UnaryBuiltinWrapper(sympy.sqrt)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now it works just fine." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "(root1, (root2, _)) = run('over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2', stack, D)[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAOAAAAAgCAYAAAALxXRVAAAABHNCSVQICAgIfAhkiAAAB4pJREFUeJztnHuwVVMcxz9ut5e6KVQa0sTIs8hFisztehSV58iMxEGDkVGDNBicaQZNHt1KoQclhHCZxhCpvKKHW4nohrl5xi1NeUUvf3zXcU/77r3P3vucu/c52p+ZPfucvV6/vfb6/dZav7X2hpiYmLylGOgWtRAxMVlwNGrHBclE4NCohcgzWgO746Ogjvtsn2QeUOQSdg3wFfBtSLIUChcAg4B94qNgjnXATXYPM185EPgMaBS1IHnIc0BJ1ELE+GYZatd5hVMPeDvwOrAzRFkKgSbm+C1qQWJ8UwncFbUQXmgBbAGOjVqQPKQfBTaUifmPQ4BtQPuoBcnEYGBz1ELkKZOInVKFTA0wPGoh0rEbgg4AVoQtSIHQkdgpFTV3oPncVqAWmAsc5zHtCtS+8wY7BTwNWBm2IAVAd+J6yQfKgMlAL6Ac2AHMB/b3kHYl0BN373+ktEDrJqOiFiQPuRc4OWoh/ofciNrcNQHTt0TOwoEe4t5gyuoUsKycY7UEKcG2NHC52VZ6FJQCy0Msbwh1C8lDQyw3bE40508Cpi9B7fhXD3FT7Tpv5vFWBWxlzlsbuNxsKz1sDgZ+RMoQBh3RLqTfQyovSkqRd/LzgOkr0NDyYw9xU+3abR23PepRJwAHIONXiTal/IWU+APgWnIwlLVm0Nic/8k24wxkW+kNhVOFDkST/TDYB3gK2AQ8HlKZQUkgo1QWMH1TtNz1KZrL+eVB4AzgUrytWf9tzk1c4lyA2kGlyXcq0ANYgpT9ZeT0mQa8iJ5XzihFFXp1LjO10BQp+JIGLCMIA4G12CvhHKBZSHIMB3ahhpUk8xD0cuBZoBptENiMvIRuz7AMeAH4HjXKn4B5wIU+ZU2QnQKeZNJPBk5AjXsT8AfwFu7ezYeBn4FjfJR3sSnvbJc4bwAb0S6wctQurG3iIOQN3w1c4qP8elgzrjXnVtaIOaQr6mk/wX+lNxQnoUZ0BPKupdMCWddtIchxNDAGGA+85yF+CTATOAx4H3gUeAXoDDyJvTOtAliIGtd84BHzv5T6997QlJpzJ+BD1KCnA1VISRagze9WJgBXAH2ANT7KS7XrWofw/VC9zEXPfIH5vcsSbwN1o5Myh7yCGEaKkEW8xy1SllyPKvp1pHQvAWNRA9oN/IJ9pYfBh0aWdC4mHGdRMXLyrAWam2tJ3HvAlsgaW+mAHvqXluv3m/xeQobFmpdf72CC7HrAKSb9RrTMk87TJuxOy/XJaC5Xju49dbT0UN7NJk+nDuZyE+7FozrSxB1nE1YCbAc+QgblAXPeiIdVhiXAMx4ECEqQSg+LkUgB0pkOtAuh7NHI6vZMu5YkuBd0LTJmKbqb/JfhPgfyQ4LsFHA5zt7wHiZstuW60ytHSQ/lTaL+801nDnJ8ZZpuFAOrTbl9bcL9GsY9GEPDOkeCVHpYdDHlH2X+FwGvekhXg7/306wG7hTkhLD2vkncFbANcDfyAG5GCpZeTlVa3Fnm2pke7seOGvzd44wM+TVGo60a7F+Y7WTyqQworx2LgSccwpoh5ZjjIZ+HqBvF+WUPw2h34y8CtwH7An/ahNfgb6jyLBqvgyq9K7Ae9XZWNphzJguUjQxuVKMKOh9ZqZ54cxZ9jb854o9pv4uRclQjZfJKNzRnbg8sBZ5Ha2Hb0RzwSmBVWvy+SEkX+igjnQrqTw1OQF7DmeiZpJNp11BX1BPPxd4Dmnq+631J6UwjVGdOdXwO6rkyKfzNwK2ofQxxiNMGbdrvDxyJhrzp/pb/tnraKWAVsqgDkDJayaax5arSs5EhE6+hRjUWKaKdobAStFcBPfQu5rfTPU01x3hghLk2CylEH2CRJf5oc05tHGgGtEVKYXUoeKXC5loC1dUMGxkykVoLrnEIv8ic3/KZrxNnoXbgZIAuQt55t15tGHoGa9Azt1v892sYbTk9gyBBGYq67lsdwseZ8PMaoGyv9EJDuXaEs/bXHK0p2R1VqD7eN/8vM2k6mutv2uTXGvjOhJ+aVsZu4Iccy54g+BzwMZw/F9EBjb6qyd33XGYjee1ohHwSb7ikH4HkXY27T2AVMqRlNmGjTR7D3EUVlcDhXiL6IOxKD0IRWl+6H3sPV5gksZ8DtjXXq6nbPAHauTHPhG1nz6F8ymkwyKacLgT7+kGC4Aq41KT9hj09si2Bt5ER7BcgXzs6AO/ivNGij5HlOofwUSZ8Be5v1fs1jK60Rx7AXBJmpWfDNDQcKY9YjiTOTph3qHO0jEVD0o3IibCT+sOc/ub6LjS6GYs80kvRQnwQEgRTwGK0rasKzaXWoV0tE9DmgF1orpUrZqC5mBMTUd3Yvax7N7rH5WR+4yKIYXSlG5qc5oKwKz0bzkcOi6g/Z5fEWQHbovlpLfLeLUbzi+7ULWhb6Y2ULzUn+QnNVwYHlC9BMAU83qSbgvbZvoLW9raihuo3Pzd6A+dmiPMdGuZbuQrJuQONhpI2R8KSxq9hzEiuPswUZqVnS3P04mdM4ZOp/Z6M2uUtNmFJMi+3LLKkCWIYY2L2WlK7gzpHLUhMzN7IF8RfOoiJiYmJiYmJiYmJiYkJgX8BwlKCUbczg2IAAAAASUVORK5CYII=\n", + "text/latex": [ + "$$\\frac{1}{2 a} \\left(- b - \\sqrt{- 4 a c + b^{2}}\\right)$$" + ], + "text/plain": [ + " _____________\n", + " ╱ 2 \n", + "-b - ╲╱ -4⋅a⋅c + b \n", + "─────────────────────\n", + " 2⋅a " + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "root1" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAOAAAAAgCAYAAAALxXRVAAAABHNCSVQICAgIfAhkiAAAB6dJREFUeJztnHeMFVUUhz9hRZCiqIhEkagRKyiuDRWzYMGGWKIm1lGJGjVqrFGjvpCIZC0gKio2sCvqaoixg11BXLDLWrLYFZSIDUHAP373sY/ZO/XNm3lP50smszu3nTlzzp17z73zICcnp2qpAwZmLUROThlsg+y4JrkJ2DRrIaqMdYGV+VFTx9XWJ1kFdPBJOwX4HPgqJVlqhZHA0cAa+VEzx2fA2baHWa1sAHwIdMxakCrkQaB71kLkROYdZNdVhdcb8GLgaWB5irLUAp3M8VvWguREpgm4PGshwtAV+BXYLmtBqpADqLGhTM4qNgGWAL2zFiSI44BFWQtRpdxCHpSqZVqBc7MWohTbEPQQYE7agtQIfcmDUllzKZrPLQYWANOA7UOWnYPsu2qwOeCewNy0BakBBpHrpRpoACYCewDDgH+AF4H1QpSdCwzGP/qfKV3RusklWQtShVwF7JK1EP9BzkQ2d0rM8t1QsHBEiLxnmLb6xWwrcdw9QVGwXyvcbrlKz4J6YHaK7Z1A20LyqBTbTZudzPndmOW7Izv+JUTeol1XzTze7YA9zHlxhdstV+lpszHwHXKGNOiLdiH9nlJ7WVKPopMfxSw/Hg0t3w6Rt2jXfuu4vdEbdQKwPur8mtCmlL+QE78OnEoCQ1l3BWua89JyKw6gXKVXCi+FjkCT/TRYA7gH+Bm4LaU24+KgTqkhZvm10HLX+2guF5Vrgb2Bowi3Zv23OXfyyTMS2UGTqfcOYDdgJnL2x1HQ507gUfS8EqMeKfTkJCt1sRZy8JkJ1+tQnjGMAOZhd8KpQOeY9UblXGAFMqwCwUPQY4EHgBa0QWARihL6PcMG4BHgG2SU3wPPAYdFlNWhPJ3vbMpPBHZExv0z8AfwPP7RzeuBH4FtI7R3hGlvP588zwAL0S6wYcgu3DaxEYqGrwSOjNB+O9wVLzDnHu6MCTIAvWnfJbrSK8XOyIi2RNG1Urqi3nVJCnJsA4wFbgReDZG/OzAF2Bx4DbgZeALYDLgbezBtPDADGdeLwA3m/3ra33ulqTfnfsAbyKDvApqRk0xHm9/dTACOB4YCH0dor2jXCzzS10F6mYae+XTz9wpXvh9oG500eNQVp2OkA+oRr/TLVCanI0U/jZzuMaARGdBK4CfsSg/CobzeGGQEja5rR5BOsKgOBXnmAV3MtQL+b8BuqDd20wc99E9d18eY+h5DHYu7rqjRQYfydD7JlF+IlnlKudekXea6PhHN5Yahey8e3UK0d46p0+sFc6xJDxNRvcjkHWdJ6w4sA95CHco15ryQEKsMM4H7QwgQlzhKD4ND+Q54EXKAUu4CNiyjzrCMRr3u4JJrBeJHQeehzqzIIFP/O/jPgaLgUJ7OZ+MdDd/NpD3kuu71yVEhRHu30P75ljIVBb6Cpht1wAem3eGW9Kgd42qMpbLBkThKD4ND+Q7Y39Sxtfm/A/BkiHKtRPs+zd3B7YqCEO63bwF/B+wJXIEigIuQg5W201yS9z5zbZ8Q92OjlWj3ODmgvjXRaKsV+wez/Uw9TTHltfEmcLtHWmfkHFND1HMdbaO4qKzWMdpu/FHgQmBt4E9LeivRhioPoPE6SOkDgPnobefmB3MO6oH8ZJhhuTYFOWgQLUhBh6JeajDhgkVfEG2O+F3J33XIOVqQM4VlIJoz9wZmAQ+jtbBlaA54IvBeSf7hyElt+gnDeNpPDXZEUcMp6JmUErRraAB6E0/DHgEtPt/5kaT0piPSmZeO90dvriCHPwe4ANnHCR55eqJN+wcDW6Ehb2m8ZdVWT5sDNqMe9RDkjG7KMbaklJ60MZTylKmnETmiraNwE/etAnro/c3fXnq9wxw3AueZa/chHQwFXnblH23OxY0DnYFeSA/ugEJYxluuOUhXky0yBFFcC271SD/cnJ+PWK8X+yJb9OqADkfReb+32lnoGXyMnrlt8T9qx2hlrwBB4jIKvbov8EgfZ9IPilG3Q/lDUFAkcDma96Wx9tcFrSnZjmZ0T6+Z/48xZfqa689a6lsX+Nqk717Sxkrg24Rld4iv81vx/rmIPmj01UJyv+fyEN6joI4oJvGMT/nzkLwf4B8TeA91pA2WtNGmjrP8RRVNwBZhMkagkkp3SMYBO6D1pTHYI1xpUsA+B+xlrrfQtnkCtHPjOZO2jNWH8sWgwdGWdvoT79cPHOLrfJYp+yWrR2S7AS+gTvCAGPXa6AO8gvdGi6FGltM80i8x6XPw/6o+asfoS28UAUySSirdIRkHBL1tlqJQd5YU8A7CvERboKURDUkXoiDCctoPcw4211eg0U0jikjPQgvxcXCIp/M6tK2rGc2lPkO7WiagzQEr0FwrKSajuZgXNyHd2D7WvQLd42yCv7iI0zH6MhBNTpOg0kp3SM4BD0UBi6x/zq6AtwP2QvPTBSh69yaaXwyibUHbzRDkfMU5yfdovnJcTPkc4ul8B1NuEtpn+wRa21uMDDVqfX4MAQ4MyPM1Gua7OQnJ+Q8aDRUsh+MqE7VjDCSpH2aqtNIdknPALujDz5zaJ8h+d0F2c74lrUDwcsvLrjJxOsacnP8txd1Bm2UtSE7O/5FPyH/pICcnJycnJycnJycnJycF/gXtdZJcHV903gAAAABJRU5ErkJggg==\n", + "text/latex": [ + "$$\\frac{1}{2 a} \\left(- b + \\sqrt{- 4 a c + b^{2}}\\right)$$" + ], + "text/plain": [ + " _____________\n", + " ╱ 2 \n", + "-b + ╲╱ -4⋅a⋅c + b \n", + "─────────────────────\n", + " 2⋅a " + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "root2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "At some point I will probably make an optional library of Joy wrappers for SymPy functions, and either load it automatically if SymPy installation is available or have a CLI switch or something. There's a huge amount of incredibly useful stuff and I don't see why Joy shouldn't expose another interface for using it. (As an example, the symbolic expressions can be \"lambdafied\" into very fast versions, i.e. a function that takes `a`, `b`, and `c` and computes the value of the root using just low-level fast code, bypassing Joy and Python. Also, Numpy, &c.)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Partial Evaluation\n", + "\n", + "[Futamura projections](https://en.wikipedia.org/wiki/Futamura_projection)\n", + "\n", + "Starting with the example from [Partial Computation of Programs](http://hdl.handle.net/2433/103401)\n", + "by [Yoshihiko Futamura](http://fi.ftmr.info/) of a function to compute `u` to the `k`th power:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "def F(u, k):\n", + " z = 1\n", + " while k != 0:\n", + " if odd(k):\n", + " z = z * u\n", + " k = k / 2\n", + " u = u * u\n", + " return z" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Partial evaluation with `k = 5`:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "def F5(u):\n", + " z = 1 * u\n", + " u = u * u\n", + " u = u * u\n", + " z = z * u\n", + " return z" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Translate `F(u, k)` to Joy\n", + "\n", + " u k 1 # z = 1\n", + " [pop] [Fw] while # the while statement\n", + " popopd # discard u k, \"return\" z" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What's Fw?\n", + "\n", + "\n", + " u k z [pop odd] [Ft] [] ifte # the if statement\n", + " [2 //] dip # k = k / 2 floordiv\n", + " [sqr] dipd # u = u * u\n", + "\n", + " [[sqr] dip 2 //] dip # We can merge last two lines." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Helper function Ft (to compute z = z * u).\n", + "\n", + "\n", + " u k z Ft\n", + " ---------------\n", + " u k u*z\n", + "\n", + "\n", + " Ft == [over] dip *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Putting it together:\n", + "\n", + " Ft == [over] dip *\n", + " Fb == [[sqr] dip 2 //] dip\n", + " Fw == [pop odd] [Ft] [] ifte Fb\n", + " F == 1 [pop] [Fw] while popopd" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "define('odd == 2 %')" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "define('Ft == [over] dip *')" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "define('Fb == [[sqr] dip 2 //] dip')" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "define('Fw == [pop odd] [Ft] [] ifte Fb')" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "define('F == 1 [pop] [Fw] while popopd')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Try it out:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "32\n" + ] + } + ], + "source": [ + "J('2 5 F')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In order to elide the tests let's define special versions of `while` and `ifte`:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "from joy.joy import joy\n", + "from joy.library import FunctionWrapper\n", + "from joy.parser import Symbol\n", + "from joy.utils.stack import concat\n", + "\n", + "\n", + "S_while = Symbol('while')\n", + "\n", + "\n", + "@FunctionWrapper\n", + "def while_(S, expression, dictionary):\n", + " '''[if] [body] while'''\n", + " (body, (if_, stack)) = S\n", + " if joy(stack, if_, dictionary)[0][0]:\n", + " expression = concat(body, (if_, (body, (S_while, expression))))\n", + " return stack, expression, dictionary\n", + "\n", + "\n", + "@FunctionWrapper\n", + "def ifte(stack, expression, dictionary):\n", + " '''[if] [then] [else] ifte'''\n", + " (else_, (then, (if_, stack))) = stack\n", + " if_res = joy(stack, if_, dictionary)[0][0]\n", + " quote = then if if_res else else_\n", + " expression = concat(quote, expression)\n", + " return (stack, expression, dictionary)\n", + "\n", + "\n", + "D['ifte'] = ifte\n", + "D['while'] = while_" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And with a SymPy symbol for the `u` argument:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " u 5 . F\n", + " u 5 . 1 [pop] [Fw] while popopd\n", + " u 5 1 . [pop] [Fw] while popopd\n", + " u 5 1 [pop] . [Fw] while popopd\n", + " u 5 1 [pop] [Fw] . while popopd\n", + " u 5 1 . Fw [pop] [Fw] while popopd\n", + " u 5 1 . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd\n", + " u 5 1 [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd\n", + " u 5 1 [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd\n", + " u 5 1 [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd\n", + " u 5 1 . Ft Fb [pop] [Fw] while popopd\n", + " u 5 1 . [over] dip * Fb [pop] [Fw] while popopd\n", + " u 5 1 [over] . dip * Fb [pop] [Fw] while popopd\n", + " u 5 . over 1 * Fb [pop] [Fw] while popopd\n", + " u 5 u . 1 * Fb [pop] [Fw] while popopd\n", + " u 5 u 1 . * Fb [pop] [Fw] while popopd\n", + " u 5 u . Fb [pop] [Fw] while popopd\n", + " u 5 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd\n", + " u 5 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd\n", + " u 5 . [sqr] dip 2 // u [pop] [Fw] while popopd\n", + " u 5 [sqr] . dip 2 // u [pop] [Fw] while popopd\n", + " u . sqr 5 2 // u [pop] [Fw] while popopd\n", + " u . dup mul 5 2 // u [pop] [Fw] while popopd\n", + " u u . mul 5 2 // u [pop] [Fw] while popopd\n", + " u**2 . 5 2 // u [pop] [Fw] while popopd\n", + " u**2 5 . 2 // u [pop] [Fw] while popopd\n", + " u**2 5 2 . // u [pop] [Fw] while popopd\n", + " u**2 2 . u [pop] [Fw] while popopd\n", + " u**2 2 u . [pop] [Fw] while popopd\n", + " u**2 2 u [pop] . [Fw] while popopd\n", + " u**2 2 u [pop] [Fw] . while popopd\n", + " u**2 2 u . Fw [pop] [Fw] while popopd\n", + " u**2 2 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd\n", + " u**2 2 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd\n", + " u**2 2 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd\n", + " u**2 2 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd\n", + " u**2 2 u . Fb [pop] [Fw] while popopd\n", + " u**2 2 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd\n", + " u**2 2 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd\n", + " u**2 2 . [sqr] dip 2 // u [pop] [Fw] while popopd\n", + " u**2 2 [sqr] . dip 2 // u [pop] [Fw] while popopd\n", + " u**2 . sqr 2 2 // u [pop] [Fw] while popopd\n", + " u**2 . dup mul 2 2 // u [pop] [Fw] while popopd\n", + " u**2 u**2 . mul 2 2 // u [pop] [Fw] while popopd\n", + " u**4 . 2 2 // u [pop] [Fw] while popopd\n", + " u**4 2 . 2 // u [pop] [Fw] while popopd\n", + " u**4 2 2 . // u [pop] [Fw] while popopd\n", + " u**4 1 . u [pop] [Fw] while popopd\n", + " u**4 1 u . [pop] [Fw] while popopd\n", + " u**4 1 u [pop] . [Fw] while popopd\n", + " u**4 1 u [pop] [Fw] . while popopd\n", + " u**4 1 u . Fw [pop] [Fw] while popopd\n", + " u**4 1 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd\n", + " u**4 1 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd\n", + " u**4 1 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd\n", + " u**4 1 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd\n", + " u**4 1 u . Ft Fb [pop] [Fw] while popopd\n", + " u**4 1 u . [over] dip * Fb [pop] [Fw] while popopd\n", + " u**4 1 u [over] . dip * Fb [pop] [Fw] while popopd\n", + " u**4 1 . over u * Fb [pop] [Fw] while popopd\n", + " u**4 1 u**4 . u * Fb [pop] [Fw] while popopd\n", + " u**4 1 u**4 u . * Fb [pop] [Fw] while popopd\n", + " u**4 1 u**5 . Fb [pop] [Fw] while popopd\n", + " u**4 1 u**5 . [[sqr] dip 2 //] dip [pop] [Fw] while popopd\n", + "u**4 1 u**5 [[sqr] dip 2 //] . dip [pop] [Fw] while popopd\n", + " u**4 1 . [sqr] dip 2 // u**5 [pop] [Fw] while popopd\n", + " u**4 1 [sqr] . dip 2 // u**5 [pop] [Fw] while popopd\n", + " u**4 . sqr 1 2 // u**5 [pop] [Fw] while popopd\n", + " u**4 . dup mul 1 2 // u**5 [pop] [Fw] while popopd\n", + " u**4 u**4 . mul 1 2 // u**5 [pop] [Fw] while popopd\n", + " u**8 . 1 2 // u**5 [pop] [Fw] while popopd\n", + " u**8 1 . 2 // u**5 [pop] [Fw] while popopd\n", + " u**8 1 2 . // u**5 [pop] [Fw] while popopd\n", + " u**8 0 . u**5 [pop] [Fw] while popopd\n", + " u**8 0 u**5 . [pop] [Fw] while popopd\n", + " u**8 0 u**5 [pop] . [Fw] while popopd\n", + " u**8 0 u**5 [pop] [Fw] . while popopd\n", + " u**8 0 u**5 . popopd\n", + " u**5 . \n" + ] + } + ], + "source": [ + "stack = list_to_stack([5, sympy.symbols('u')])\n", + "viewer = TracePrinter()\n", + "try:\n", + " (result, _) = run('F', stack, D, viewer.viewer)[0]\n", + "except Exception, e:\n", + " print e\n", + "viewer.print_()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAABgAAAAWCAYAAADafVyIAAAABHNCSVQICAgIfAhkiAAAAUpJREFUSInt1DtLXUEUBeBPESLXFGITsDEqEdHKaLBMQBvFRsh/SG9nKouAjY0/IsQm2lgoNrENeaEgaKGVjyBaBLQQNRYzSYbjOVduzoWkcMGwmb1n1pr9YPjPMYOfmXWUHmiqg8g2XiT7q3oLXMq8OkVjHQS6sI89vMPjNNhQknwMFezgEabRh36cluTORQu+Y+qXI1ui18IkTOZc7oixxSoCZ9jCkyKBp9F+zrk8FO3XKgLN6MVh0YE9HBfEZoUMJhLfHJ6jE8NYxg8h21toiwQrBQJrMd6e+BZwgAthkt4LTc7FaCR4UxA/ERpYE9IeDEabV/8uIcMvZQQGos0jGY+2WoPvxDehllk8wKZQvpdlBD5Gkp7E14K3/vyU3bWSpp/dKp5hHUt4iBFsCHNdwe5fPPw3mjEvjN05PuEVWnGND2XI7/HvcAOaJEK/e6V+UwAAAABJRU5ErkJggg==\n", + "text/latex": [ + "$$u^{5}$$" + ], + "text/plain": [ + " 5\n", + "u " + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's try partial evaluation by hand and use a \"stronger\" thunk.\n", + "\n", + "Caret underscoring indicates terms that form thunks. When an arg is unavailable for a computation we just postpone it until the arg becomes available and in the meantime treat the pending computation as one unit.\n", + "\n", + " u 5 . F\n", + " u 5 . 1 [pop] [Fw] while popopd\n", + " u 5 1 . [pop] [Fw] while popopd\n", + " u 5 1 [pop] . [Fw] while popopd\n", + " u 5 1 [pop] [Fw] . while popopd\n", + " u 5 1 . Fw [pop] [Fw] while popopd\n", + " u 5 1 . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd\n", + " u 5 1 [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd\n", + " u 5 1 [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd\n", + " u 5 1 [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd\n", + " u 5 1 . Ft Fb [pop] [Fw] while popopd\n", + " u 5 1 . [over] dip * Fb [pop] [Fw] while popopd\n", + " u 5 1 [over] . dip * Fb [pop] [Fw] while popopd\n", + " u 5 . over 1 * Fb [pop] [Fw] while popopd\n", + " u 5 u . 1 * Fb [pop] [Fw] while popopd\n", + " u 5 u 1 . * Fb [pop] [Fw] while popopd\n", + " u 5 u . Fb [pop] [Fw] while popopd\n", + " u 5 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd\n", + " u 5 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd\n", + " u 5 . [sqr] dip 2 // u [pop] [Fw] while popopd\n", + " u 5 [sqr] . dip 2 // u [pop] [Fw] while popopd\n", + " u . sqr 5 2 // u [pop] [Fw] while popopd\n", + " u . dup mul 5 2 // u [pop] [Fw] while popopd\n", + " u dup * . 5 2 // u [pop] [Fw] while popopd\n", + " ^^^^^^^" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " u dup * 2 u [pop] [Fw] . while popopd\n", + " u dup * 2 u . Fw [pop] [Fw] while popopd\n", + " u dup * 2 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd\n", + " u dup * 2 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd\n", + " u dup * 2 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd\n", + " u dup * 2 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd\n", + " u dup * 2 u . Fb [pop] [Fw] while popopd\n", + " u dup * 2 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd\n", + " u dup * 2 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd\n", + " u dup * 2 . [sqr] dip 2 // u [pop] [Fw] while popopd\n", + " u dup * 2 [sqr] . dip 2 // u [pop] [Fw] while popopd\n", + " u dup * . sqr 2 2 // u [pop] [Fw] while popopd\n", + " u dup * . dup mul 2 2 // u [pop] [Fw] while popopd\n", + " u dup * dup * . 2 2 // u [pop] [Fw] while popopd\n", + " ^^^^^^^^^^^^^\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "w/ `K == u dup * dup *`\n", + "\n", + " K 1 u [pop] [Fw] . while popopd\n", + " K 1 u . Fw [pop] [Fw] while popopd\n", + " K 1 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd\n", + " K 1 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd\n", + " K 1 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd\n", + " K 1 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd\n", + " K 1 u . Ft Fb [pop] [Fw] while popopd\n", + " K 1 u . [over] dip * Fb [pop] [Fw] while popopd\n", + " K 1 u [over] . dip * Fb [pop] [Fw] while popopd\n", + " K 1 . over u * Fb [pop] [Fw] while popopd\n", + " K 1 K . u * Fb [pop] [Fw] while popopd\n", + " K 1 K u . * Fb [pop] [Fw] while popopd\n", + " K 1 K u * . Fb [pop] [Fw] while popopd\n", + " ^^^^^" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "w/ `L == K u *`\n", + "\n", + " K 1 L . Fb [pop] [Fw] while popopd\n", + " K 1 L . [[sqr] dip 2 //] dip [pop] [Fw] while popopd\n", + " K 1 L [[sqr] dip 2 //] . dip [pop] [Fw] while popopd\n", + " K 1 . [sqr] dip 2 // L [pop] [Fw] while popopd\n", + " K 1 [sqr] . dip 2 // L [pop] [Fw] while popopd\n", + " K . sqr 1 2 // L [pop] [Fw] while popopd\n", + " K . dup mul 1 2 // L [pop] [Fw] while popopd\n", + " K K . mul 1 2 // L [pop] [Fw] while popopd\n", + " K K * . 1 2 // L [pop] [Fw] while popopd\n", + " ^^^^^\n", + " K K * . 1 2 // L [pop] [Fw] while popopd\n", + " K K * 1 . 2 // L [pop] [Fw] while popopd\n", + " K K * 1 2 . // L [pop] [Fw] while popopd\n", + " K K * 0 . L [pop] [Fw] while popopd\n", + " K K * 0 L . [pop] [Fw] while popopd\n", + " K K * 0 L [pop] . [Fw] while popopd\n", + " K K * 0 L [pop] [Fw] . while popopd\n", + " ^^^^^\n", + " K K * 0 L . popopd\n", + " L . " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So:\n", + "\n", + " K == u dup * dup *\n", + " L == K u *\n", + "\n", + "Our result \"thunk\" would be:\n", + " \n", + " u dup * dup * u *\n", + "\n", + "Mechanically, you could do:\n", + "\n", + " u dup * dup * u *\n", + " u u [dup * dup *] dip *\n", + " u dup [dup * dup *] dip *\n", + "\n", + "\n", + " F5 == dup [dup * dup *] dip *\n", + "\n", + "But we can swap the two arguments to the final `*` to get all mentions of `u` to the left:\n", + "\n", + "\n", + " u u dup * dup * *\n", + "\n", + "\n", + "Then de-duplicate \"u\":\n", + "\n", + "\n", + " u dup dup * dup * *\n", + "\n", + "\n", + "To arrive at a startlingly elegant form for F5:\n", + "\n", + "\n", + " F5 == dup dup * dup * *" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " u . dup dup * dup * *\n", + " u u . dup * dup * *\n", + " u u u . * dup * *\n", + " u u**2 . dup * *\n", + "u u**2 u**2 . * *\n", + " u u**4 . *\n", + " u**5 . \n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAABgAAAAWCAYAAADafVyIAAAABHNCSVQICAgIfAhkiAAAAUpJREFUSInt1DtLXUEUBeBPESLXFGITsDEqEdHKaLBMQBvFRsh/SG9nKouAjY0/IsQm2lgoNrENeaEgaKGVjyBaBLQQNRYzSYbjOVduzoWkcMGwmb1n1pr9YPjPMYOfmXWUHmiqg8g2XiT7q3oLXMq8OkVjHQS6sI89vMPjNNhQknwMFezgEabRh36cluTORQu+Y+qXI1ui18IkTOZc7oixxSoCZ9jCkyKBp9F+zrk8FO3XKgLN6MVh0YE9HBfEZoUMJhLfHJ6jE8NYxg8h21toiwQrBQJrMd6e+BZwgAthkt4LTc7FaCR4UxA/ERpYE9IeDEabV/8uIcMvZQQGos0jGY+2WoPvxDehllk8wKZQvpdlBD5Gkp7E14K3/vyU3bWSpp/dKp5hHUt4iBFsCHNdwe5fPPw3mjEvjN05PuEVWnGND2XI7/HvcAOaJEK/e6V+UwAAAABJRU5ErkJggg==\n", + "text/latex": [ + "$$u^{5}$$" + ], + "text/plain": [ + " 5\n", + "u " + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "stack = list_to_stack([sympy.symbols('u')])\n", + "viewer = TracePrinter()\n", + "try:\n", + " (result, _) = run('dup dup * dup * *', stack, D, viewer.viewer)[0]\n", + "except Exception, e:\n", + " print e\n", + "viewer.print_()\n", + "result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "I'm not sure how to implement these kinds of thunks. I think you have to have support in the interpreter, or you have to modify all of the functions like `dup` to check for thunks in their inputs." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Working on the compiler, from this:\n", + "\n", + " dup dup * dup * *\n", + "\n", + "We can already generate:\n", + "\n", + " ---------------------------------\n", + " (a0, stack) = stack\n", + " a1 = mul(a0, a0)\n", + " a2 = mul(a1, a1)\n", + " a3 = mul(a2, a0)\n", + " stack = (a3, stack)\n", + " ---------------------------------\n", + "\n", + "This is pretty old stuff... (E.g. from 1999, M. Anton Ertl [Compilation of Stack-Based Languages](http://www.complang.tuwien.ac.at/projects/rafts.html) he goes a lot further for Forth.)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## \"A Transformation Based Approach to Semantics-Directed Code Generation\"\n", + "\n", + "by Arthur Nunes-Harwitt\n", + "\n", + "https://dl.acm.org/citation.cfm?doid=2635648.2635652\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6\n" + ] + } + ], + "source": [ + "def m(x, y): return x * y\n", + "\n", + "print m(2, 3)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6\n" + ] + } + ], + "source": [ + "def m(x): return lambda y: x * y\n", + "\n", + "print m(2)(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lambda y: 2 * y\n", + "6\n" + ] + } + ], + "source": [ + "def m(x): return \"lambda y: %(x)s * y\" % locals()\n", + "\n", + "print m(2)\n", + "print eval(m(2))(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In Joy:\n", + "\n", + " m == [*] cons\n", + "\n", + " 3 2 m i\n", + " 3 2 [*] cons i\n", + " 3 [2 *] i\n", + " 3 2 *\n", + " 6\n" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lambda b: 1 if 3 == 0 else b * p(3 - 1, b)\n" + ] + } + ], + "source": [ + "def p(n, b): # original\n", + " return 1 if n == 0 else b * p(n - 1, b)\n", + "\n", + "\n", + "def p(n): # curried\n", + " return lambda b: 1 if n == 0 else b * p(n - 1, b)\n", + "\n", + "\n", + "def p(n): # quoted\n", + " return \"lambda b: 1 if %(n)s == 0 else b * p(%(n)s - 1, b)\" % locals()\n", + "\n", + "\n", + "print p(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Original\n", + "\n", + " p == [0 =] [popop 1] [-- over] [dip *] genrec\n", + "\n", + " b n p\n", + " b n [0 =] [popop 1] [-- over [p] dip *]\n", + "\n", + " b n -- over [p] dip *\n", + " b n-1 over [p] dip *\n", + " b n-1 b [p] dip *\n", + " b n-1 p b *\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "curried, quoted\n", + "\n", + " n p\n", + " ---------------------------------------------\n", + " [[n 0 =] [pop 1] [dup n --] [*] genrec]" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lambda b: b * p(3 - 1, b)\n" + ] + } + ], + "source": [ + "def p(n): # lambda lowered\n", + " return (\n", + " lambda b: 1\n", + " if n == 0 else\n", + " lambda b: b * p(n - 1, b)\n", + " )\n", + "\n", + "\n", + "def p(n): # lambda lowered quoted\n", + " return (\n", + " \"lambda b: 1\"\n", + " if n == 0 else\n", + " \"lambda b: b * p(%(n)s - 1, b)\" % locals()\n", + " )\n", + "\n", + "print p(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " p == [0 =] [[pop 1]] [ [-- [dup] dip p *] cons ]ifte\n", + "\n", + "\n", + " 3 p\n", + " 3 [-- [dup] dip p *] cons\n", + " [3 -- [dup] dip p *]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "8\n" + ] + } + ], + "source": [ + "def p(n): # expression lifted\n", + " if n == 0:\n", + " return lambda b: 1\n", + " f = p(n - 1)\n", + " return lambda b: b * f(b)\n", + "\n", + "\n", + "print p(3)(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lambda b: b * (lambda b: b * (lambda b: b * (lambda b: 1)(b))(b))(b)\n", + "8\n" + ] + } + ], + "source": [ + "def p(n): # quoted\n", + " if n == 0:\n", + " return \"lambda b: 1\"\n", + " f = p(n - 1)\n", + " return \"lambda b: b * (%(f)s)(b)\" % locals()\n", + "\n", + "print p(3)\n", + "print eval(p(3))(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " p == [0 =] [pop [pop 1]] [-- p [dupdip *] cons] ifte\n", + "\n", + "\n", + " 3 p\n", + " 3 -- p [dupdip *] cons\n", + " 2 p [dupdip *] cons\n", + " 2 -- p [dupdip *] cons [dupdip *] cons\n", + " 1 p [dupdip *] cons [dupdip *] cons\n", + " 1 -- p [dupdip *] cons [dupdip *] cons [dupdip *] cons\n", + " 0 p [dupdip *] cons [dupdip *] cons [dupdip *] cons\n", + " 0 pop [pop 1] [dupdip *] cons [dupdip *] cons [dupdip *] cons\n", + " [pop 1] [dupdip *] cons [dupdip *] cons [dupdip *] cons\n", + " ...\n", + " [[[[pop 1] dupdip *] dupdip *] dupdip *]\n", + "\n", + "\n", + " 2 [[[[pop 1] dupdip *] dupdip *] dupdip *] i\n", + " 2 [[[pop 1] dupdip *] dupdip *] dupdip *\n", + " 2 [[pop 1] dupdip *] dupdip * 2 *\n", + " 2 [pop 1] dupdip * 2 * 2 *\n", + " 2 pop 1 2 * 2 * 2 *\n", + " 1 2 * 2 * 2 *\n", + "\n", + "\n", + "\n", + " p == [0 =] [pop [pop 1]] [-- p [dupdip *] cons] ifte\n", + " p == [0 =] [pop [pop 1]] [-- [p] i [dupdip *] cons] ifte\n", + " p == [0 =] [pop [pop 1]] [--] [i [dupdip *] cons] genrec" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [], + "source": [ + "define('p == [0 =] [pop [pop 1]] [--] [i [dupdip *] cons] genrec')" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[[[pop 1] dupdip *] dupdip *] dupdip *]\n" + ] + } + ], + "source": [ + "J('3 p')" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " . 2 [[[[pop 1] dupdip *] dupdip *] dupdip *] i\n", + " 2 . [[[[pop 1] dupdip *] dupdip *] dupdip *] i\n", + "2 [[[[pop 1] dupdip *] dupdip *] dupdip *] . i\n", + " 2 . [[[pop 1] dupdip *] dupdip *] dupdip *\n", + " 2 [[[pop 1] dupdip *] dupdip *] . dupdip *\n", + " 2 . [[pop 1] dupdip *] dupdip * 2 *\n", + " 2 [[pop 1] dupdip *] . dupdip * 2 *\n", + " 2 . [pop 1] dupdip * 2 * 2 *\n", + " 2 [pop 1] . dupdip * 2 * 2 *\n", + " 2 . pop 1 2 * 2 * 2 *\n", + " . 1 2 * 2 * 2 *\n", + " 1 . 2 * 2 * 2 *\n", + " 1 2 . * 2 * 2 *\n", + " 2 . 2 * 2 *\n", + " 2 2 . * 2 *\n", + " 4 . 2 *\n", + " 4 2 . *\n", + " 8 . \n" + ] + } + ], + "source": [ + "V('2 [[[[pop 1] dupdip *] dupdip *] dupdip *] i')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stack = list_to_stack([sympy.symbols('u')])\n", + "(result, s) = run('p i', stack, D)[0]\n", + "result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From this:\n", + "\n", + " p == [0 =] [pop pop 1] [-- over] [dip *] genrec\n", + "\n", + "To this:\n", + "\n", + " p == [0 =] [pop [pop 1]] [--] [i [dupdip *] cons] genrec\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Try it with `F()`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def odd(n): return n % 2\n", + "\n", + "\n", + "def F(u, k):\n", + " z = 1\n", + " while k != 0:\n", + " if odd(k):\n", + " z = z * u\n", + " k = k / 2\n", + " u = u * u\n", + " return z\n", + "\n", + "F(2, 5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def F(k):\n", + " def _F(u, k=k):\n", + " z = 1\n", + " while k != 0:\n", + " if odd(k):\n", + " z = z * u\n", + " k = k / 2\n", + " u = u * u\n", + " return z\n", + " return _F\n", + " \n", + "F(5)(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def F(k):\n", + " def _F(u, k=k):\n", + " if k == 0:\n", + " z = 1\n", + " else:\n", + " z = F(k / 2)(u)\n", + " z *= z\n", + " if odd(k):\n", + " z = z * u\n", + " return z\n", + " return _F\n", + " \n", + "F(5)(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def F(k):\n", + " if k == 0:\n", + " z = lambda u: 1\n", + " else:\n", + " f = F(k / 2)\n", + " def z(u):\n", + " uu = f(u)\n", + " uu *= uu\n", + " return uu * u if odd(k) else uu\n", + " return z\n", + " \n", + "F(5)(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def F(k):\n", + " if k == 0:\n", + " z = lambda u: 1\n", + " else:\n", + " f = F(k / 2)\n", + " if odd(k):\n", + " z = lambda u: (lambda fu, u: fu * fu * u)(f(u), u)\n", + " else:\n", + " z = lambda u: (lambda fu, u: fu * fu)(f(u), u)\n", + " return z\n", + " \n", + "F(5)(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def F(k):\n", + " if k == 0:\n", + " z = \"lambda u: 1\"\n", + " else:\n", + " f = F(k / 2)\n", + " if odd(k):\n", + " z = \"lambda u: (lambda fu, u: fu * fu * u)((%(f)s)(u), u)\" % locals()\n", + " else:\n", + " z = \"lambda u: (lambda fu, u: fu * fu)((%(f)s)(u), u)\" % locals()\n", + " return z\n", + " \n", + "source = F(5)\n", + "print source\n", + "eval(source)(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Hmm..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for n in range(4):\n", + " print F(n)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def F(k):\n", + " if k == 0:\n", + " z = \"lambda u: 1\"\n", + " elif k == 1:\n", + " z = \"lambda u: u\"\n", + " else:\n", + " f = F(k / 2)\n", + " if odd(k):\n", + " z = \"lambda u: (lambda fu, u: fu * fu * u)((%(f)s)(u), u)\" % locals()\n", + " else:\n", + " z = \"lambda u: (lambda fu, u: fu * fu)((%(f)s)(u), u)\" % locals()\n", + " return z\n", + " \n", + "source = F(5)\n", + "print source\n", + "eval(source)(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for n in range(4):\n", + " print F(n)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def F(k):\n", + " if k == 0:\n", + " z = \"lambda u: 1\"\n", + " elif k == 1:\n", + " z = \"lambda u: u\"\n", + " else:\n", + " m = k / 2\n", + " if odd(k):\n", + " if m == 0:\n", + " z = \"lambda u: 1\"\n", + " elif m == 1:\n", + " z = \"lambda u: u * u * u\"\n", + " else:\n", + " z = \"lambda u: (lambda fu, u: fu * fu * u)((%s)(u), u)\" % F(m)\n", + " else:\n", + " if m == 0:\n", + " z = \"lambda u: 1\"\n", + " elif m == 1:\n", + " z = \"lambda u: u * u\"\n", + " else:\n", + " z = \"lambda u: (lambda u: u * u)((%s)(u))\" % F(m)\n", + " return z\n", + " \n", + "source = F(5)\n", + "print source\n", + "eval(source)(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def F(k):\n", + " if k == 0:\n", + " z = \"lambda u: 1\"\n", + " elif k == 1:\n", + " z = \"lambda u: u\"\n", + " else:\n", + " m = k / 2\n", + " if m == 0:\n", + " z = \"lambda u: 1\"\n", + " \n", + " elif odd(k):\n", + " if m == 1:\n", + " z = \"lambda u: u * u * u\"\n", + " else:\n", + " z = \"lambda u: (lambda fu, u: fu * fu * u)((%s)(u), u)\" % F(m)\n", + " else:\n", + " if m == 1:\n", + " z = \"lambda u: u * u\"\n", + " else:\n", + " z = \"lambda u: (lambda u: u * u)((%s)(u))\" % F(m)\n", + " return z\n", + " \n", + "source = F(5)\n", + "print source\n", + "eval(source)(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "for n in range(7):\n", + " source = F(n)\n", + " print n, '%2i' % eval(source)(2), source" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So that gets pretty good, eh?\n", + "\n", + "\n", + "But looking back at the definition in Joy, it doesn't seem easy to directly apply this technique to Joy code:\n", + "\n", + " Ft == [over] dip *\n", + " Fb == [[sqr] dip 2 //] dip\n", + " Fw == [pop odd] [Ft] [] ifte Fb\n", + " F == 1 [pop] [Fw] while popopd\n", + "\n", + "\n", + "But a direct translation of the Python code..?\n", + "\n", + "\n", + " F == [\n", + " [[0 =] [pop 1]]\n", + " [[1 =] []]\n", + " [_F.0]\n", + " ] cond\n", + "\n", + " _F.0 == dup 2 // [\n", + " [[0 =] [pop 1]]\n", + " [[pop odd] _F.1]\n", + " [_F.2]\n", + " ] cond\n", + "\n", + " _F.1 == [1 =] [pop [dup dup * *]] [popd F [dupdip over * *] cons] ifte\n", + " _F.2 == [1 =] [pop [dup *]] [popd F [i dup *] cons] ifte\n", + "\n", + "\n", + "Try it:\n", + "\n", + " 5 F\n", + " 5 [ [[0 =] [pop 1]] [[1 =] []] [_F.0] ] cond\n", + " 5 _F.0\n", + " 5 dup 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond\n", + " 5 5 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond\n", + "\n", + " 5 2 [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond\n", + " 5 2 _F.1\n", + " 5 2 [1 =] [popop [dup dup * *]] [popd F [dupdip over * *] cons] ifte\n", + " 5 2 popd F [dupdip over * *] cons\n", + " 2 F [dupdip over * *] cons\n", + "\n", + " 2 F [dupdip over * *] cons\n", + "\n", + " 2 F\n", + " 2 [ [[0 =] [pop 1]] [[1 =] []] [_F.0] ] cond\n", + " 2 _F.0\n", + " 2 dup 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond\n", + " 2 2 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond\n", + " 2 1 [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond\n", + " 2 1 _F.2\n", + " 2 1 [1 =] [popop [dup *]] [popd F [i dup *] cons] ifte\n", + " 2 1 popop [dup *]\n", + " [dup *]\n", + "\n", + "\n", + " 2 F [dupdip over * *] cons\n", + " [dup *] [dupdip over * *] cons\n", + " [[dup *] dupdip over * *]\n", + "\n", + "And here it is in action:\n", + "\n", + " 2 [[dup *] dupdip over * *] i\n", + " 2 [dup *] dupdip over * *\n", + " 2 dup * 2 over * *\n", + " 2 2 * 2 over * *\n", + " 4 2 over * *\n", + " 4 2 4 * *\n", + " 4 8 *\n", + " 32\n", + "\n", + "\n", + "\n", + "So, it works, but in this case the results of the partial evaluation are more elegant.\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Try it with `hylomorphism()`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def hylomorphism(c, F, P, G):\n", + " '''Return a hylomorphism function H.'''\n", + "\n", + " def H(a):\n", + " if P(a):\n", + " result = c\n", + " else:\n", + " b, aa = G(a)\n", + " result = F(b, H(aa))\n", + " return result\n", + "\n", + " return H" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### First, curry\n", + "\n", + "With abuse of syntax:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def hylomorphism(c):\n", + " return lambda F: lambda P: lambda G: lambda a: (\n", + " if P(a):\n", + " result = c\n", + " else:\n", + " b, aa = G(a)\n", + " result = F(b)(H(aa))\n", + " return result\n", + " )\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### lambda lowering\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def hylomorphism(c):\n", + " def r(a):\n", + " def rr(P):\n", + " if P(a):\n", + " return lambda F: lambda G: c\n", + " return lambda F: lambda G: (\n", + " b, aa = G(a)\n", + " return F(b)(H(aa))\n", + " )\n", + " return rr\n", + " return r\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### expression lifting\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def hylomorphism(c):\n", + " def r(a):\n", + " def rr(P):\n", + " def rrr(G):\n", + " if P(a):\n", + " return lambda F: c\n", + " b, aa = G(a)\n", + " H = hylomorphism(c)(aa)(P)(G)\n", + " return lambda F: F(b)(H(F))\n", + " return rrr\n", + " return rr\n", + " return r\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### quoted\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def hylomorphism(c):\n", + " def r(a):\n", + " def rr(P):\n", + " def rrr(G):\n", + " if P(a):\n", + " return \"lambda F: %s\" % (c,)\n", + " b, aa = G(a)\n", + " H = hylomorphism(c)(aa)(P)(G)\n", + " return \"lambda F: F(%(b)s)((%(H)s)(F))\" % locals()\n", + " return rrr\n", + " return rr\n", + " return r\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hylomorphism(0)(3)(lambda n: n == 0)(lambda n: (n-1, n-1))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def F(a):\n", + " def _F(b):\n", + " print a, b\n", + " return a + b\n", + " return _F\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "F(2)(3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eval(hylomorphism(0)(5)(lambda n: n == 0)(lambda n: (n-1, n-1)))(F)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eval(hylomorphism([])(5)(lambda n: n == 0)(lambda n: (n-1, n-1)))(lambda a: lambda b: [a] + b)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hylomorphism(0)([1, 2, 3])(lambda n: not n)(lambda n: (n[0], n[1:]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hylomorphism([])([1, 2, 3])(lambda n: not n)(lambda n: (n[1:], n[1:]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eval(hylomorphism([])([1, 2, 3])(lambda n: not n)(lambda n: (n[1:], n[1:])))(lambda a: lambda b: [a] + b)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def hylomorphism(c):\n", + " return lambda a: lambda P: (\n", + " if P(a):\n", + " result = lambda F: lambda G: c\n", + " else:\n", + " result = lambda F: lambda G: (\n", + " b, aa = G(a)\n", + " return F(b)(H(aa))\n", + " )\n", + " return result\n", + " )\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def hylomorphism(c):\n", + " return lambda a: (\n", + " lambda F: lambda P: lambda G: c\n", + " if P(a) else\n", + " lambda F: lambda P: lambda G: (\n", + " b, aa = G(a)\n", + " return F(b)(H(aa))\n", + " )\n", + " )\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def hylomorphism(c):\n", + " return lambda a: lambda G: (\n", + " lambda F: lambda P: c\n", + " if P(a) else\n", + " b, aa = G(a)\n", + " lambda F: lambda P: F(b)(H(aa))\n", + " )\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.15" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/with_sympy.md b/docs/with_sympy.md new file mode 100644 index 0000000..6422c88 --- /dev/null +++ b/docs/with_sympy.md @@ -0,0 +1,1261 @@ + +# Symbolic Evaluation with SymPy + + +```python +import sympy + +from joy.joy import run +from joy.library import UnaryBuiltinWrapper +from joy.utils.pretty_print import TracePrinter +from joy.utils.stack import list_to_stack + +from notebook_preamble import D, J, V, define +``` + + +```python +sympy.init_printing() +``` + +## [SypPy Symbols](http://docs.sympy.org/latest/modules/core.html#module-sympy.core.symbol) + +The SymPy package provides a powerful and elegant ["thunk"](https://en.wikipedia.org/wiki/Thunk) object that can take the place of a numeric value in calculations and "record" the operations performed on it. + +We can create some of these objects and put them on the Joy stack: + + +```python +stack = list_to_stack(sympy.symbols('c a b')) +``` + +If we evaluate the `quadratic` program + + over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2 + +The [SypPy Symbols](http://docs.sympy.org/latest/modules/core.html#module-sympy.core.symbol) will become the symbolic expression of the math operations. Unfortunately, the library `sqrt` function doesn't work with the SymPy objects: + + +```python +viewer = TracePrinter() +try: + run('over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2', stack, D, viewer.viewer) +except Exception, e: + print e +viewer.print_() +``` + + can't convert expression to float + b a c . over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2 + b a c a . [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2 + b a c a [[[neg] dupdip sqr 4] dipd * * - sqrt pm] . dip 2 * [/] cons app2 + b a c . [[neg] dupdip sqr 4] dipd * * - sqrt pm a 2 * [/] cons app2 + b a c [[neg] dupdip sqr 4] . dipd * * - sqrt pm a 2 * [/] cons app2 + b . [neg] dupdip sqr 4 a c * * - sqrt pm a 2 * [/] cons app2 + b [neg] . dupdip sqr 4 a c * * - sqrt pm a 2 * [/] cons app2 + b . neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2 + -b . b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2 + -b b . sqr 4 a c * * - sqrt pm a 2 * [/] cons app2 + -b b . dup mul 4 a c * * - sqrt pm a 2 * [/] cons app2 + -b b b . mul 4 a c * * - sqrt pm a 2 * [/] cons app2 + -b b**2 . 4 a c * * - sqrt pm a 2 * [/] cons app2 + -b b**2 4 . a c * * - sqrt pm a 2 * [/] cons app2 + -b b**2 4 a . c * * - sqrt pm a 2 * [/] cons app2 + -b b**2 4 a c . * * - sqrt pm a 2 * [/] cons app2 + -b b**2 4 a*c . * - sqrt pm a 2 * [/] cons app2 + -b b**2 4*a*c . - sqrt pm a 2 * [/] cons app2 + -b -4*a*c + b**2 . sqrt pm a 2 * [/] cons app2 + + +We can pick out that first symbolic expression obect from the Joy stack: + + +```python +S, E = viewer.history[-1] +``` + + +```python +q = S[0] +q +``` + + + + +$$- 4 a c + b^{2}$$ + + + +The Python `math.sqrt()` function causes the "can't convert expression to float" exception but `sympy.sqrt()` does not: + + +```python +sympy.sqrt(q) +``` + + + + +$$\sqrt{- 4 a c + b^{2}}$$ + + + +## Use `sympy.sqrt` +This is easy to fix. + + +```python +D['sqrt'] = UnaryBuiltinWrapper(sympy.sqrt) +``` + +Now it works just fine. + + +```python +(root1, (root2, _)) = run('over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2', stack, D)[0] +``` + + +```python +root1 +``` + + + + +$$\frac{1}{2 a} \left(- b - \sqrt{- 4 a c + b^{2}}\right)$$ + + + + +```python +root2 +``` + + + + +$$\frac{1}{2 a} \left(- b + \sqrt{- 4 a c + b^{2}}\right)$$ + + + +At some point I will probably make an optional library of Joy wrappers for SymPy functions, and either load it automatically if SymPy installation is available or have a CLI switch or something. There's a huge amount of incredibly useful stuff and I don't see why Joy shouldn't expose another interface for using it. (As an example, the symbolic expressions can be "lambdafied" into very fast versions, i.e. a function that takes `a`, `b`, and `c` and computes the value of the root using just low-level fast code, bypassing Joy and Python. Also, Numpy, &c.) + +## Partial Evaluation + +[Futamura projections](https://en.wikipedia.org/wiki/Futamura_projection) + +Starting with the example from [Partial Computation of Programs](http://hdl.handle.net/2433/103401) +by [Yoshihiko Futamura](http://fi.ftmr.info/) of a function to compute `u` to the `k`th power: + + +```python +def F(u, k): + z = 1 + while k != 0: + if odd(k): + z = z * u + k = k / 2 + u = u * u + return z +``` + +Partial evaluation with `k = 5`: + + +```python +def F5(u): + z = 1 * u + u = u * u + u = u * u + z = z * u + return z +``` + +Translate `F(u, k)` to Joy + + u k 1 # z = 1 + [pop] [Fw] while # the while statement + popopd # discard u k, "return" z + +What's Fw? + + + u k z [pop odd] [Ft] [] ifte # the if statement + [2 //] dip # k = k / 2 floordiv + [sqr] dipd # u = u * u + + [[sqr] dip 2 //] dip # We can merge last two lines. + +Helper function Ft (to compute z = z * u). + + + u k z Ft + --------------- + u k u*z + + + Ft == [over] dip * + +Putting it together: + + Ft == [over] dip * + Fb == [[sqr] dip 2 //] dip + Fw == [pop odd] [Ft] [] ifte Fb + F == 1 [pop] [Fw] while popopd + + +```python +define('odd == 2 %') +``` + + +```python +define('Ft == [over] dip *') +``` + + +```python +define('Fb == [[sqr] dip 2 //] dip') +``` + + +```python +define('Fw == [pop odd] [Ft] [] ifte Fb') +``` + + +```python +define('F == 1 [pop] [Fw] while popopd') +``` + +Try it out: + + +```python +J('2 5 F') +``` + + 32 + + +In order to elide the tests let's define special versions of `while` and `ifte`: + + +```python +from joy.joy import joy +from joy.library import FunctionWrapper +from joy.parser import Symbol +from joy.utils.stack import concat + + +S_while = Symbol('while') + + +@FunctionWrapper +def while_(S, expression, dictionary): + '''[if] [body] while''' + (body, (if_, stack)) = S + if joy(stack, if_, dictionary)[0][0]: + expression = concat(body, (if_, (body, (S_while, expression)))) + return stack, expression, dictionary + + +@FunctionWrapper +def ifte(stack, expression, dictionary): + '''[if] [then] [else] ifte''' + (else_, (then, (if_, stack))) = stack + if_res = joy(stack, if_, dictionary)[0][0] + quote = then if if_res else else_ + expression = concat(quote, expression) + return (stack, expression, dictionary) + + +D['ifte'] = ifte +D['while'] = while_ +``` + +And with a SymPy symbol for the `u` argument: + + +```python +stack = list_to_stack([5, sympy.symbols('u')]) +viewer = TracePrinter() +try: + (result, _) = run('F', stack, D, viewer.viewer)[0] +except Exception, e: + print e +viewer.print_() +``` + + u 5 . F + u 5 . 1 [pop] [Fw] while popopd + u 5 1 . [pop] [Fw] while popopd + u 5 1 [pop] . [Fw] while popopd + u 5 1 [pop] [Fw] . while popopd + u 5 1 . Fw [pop] [Fw] while popopd + u 5 1 . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd + u 5 1 [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd + u 5 1 [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd + u 5 1 [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd + u 5 1 . Ft Fb [pop] [Fw] while popopd + u 5 1 . [over] dip * Fb [pop] [Fw] while popopd + u 5 1 [over] . dip * Fb [pop] [Fw] while popopd + u 5 . over 1 * Fb [pop] [Fw] while popopd + u 5 u . 1 * Fb [pop] [Fw] while popopd + u 5 u 1 . * Fb [pop] [Fw] while popopd + u 5 u . Fb [pop] [Fw] while popopd + u 5 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd + u 5 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd + u 5 . [sqr] dip 2 // u [pop] [Fw] while popopd + u 5 [sqr] . dip 2 // u [pop] [Fw] while popopd + u . sqr 5 2 // u [pop] [Fw] while popopd + u . dup mul 5 2 // u [pop] [Fw] while popopd + u u . mul 5 2 // u [pop] [Fw] while popopd + u**2 . 5 2 // u [pop] [Fw] while popopd + u**2 5 . 2 // u [pop] [Fw] while popopd + u**2 5 2 . // u [pop] [Fw] while popopd + u**2 2 . u [pop] [Fw] while popopd + u**2 2 u . [pop] [Fw] while popopd + u**2 2 u [pop] . [Fw] while popopd + u**2 2 u [pop] [Fw] . while popopd + u**2 2 u . Fw [pop] [Fw] while popopd + u**2 2 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd + u**2 2 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd + u**2 2 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd + u**2 2 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd + u**2 2 u . Fb [pop] [Fw] while popopd + u**2 2 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd + u**2 2 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd + u**2 2 . [sqr] dip 2 // u [pop] [Fw] while popopd + u**2 2 [sqr] . dip 2 // u [pop] [Fw] while popopd + u**2 . sqr 2 2 // u [pop] [Fw] while popopd + u**2 . dup mul 2 2 // u [pop] [Fw] while popopd + u**2 u**2 . mul 2 2 // u [pop] [Fw] while popopd + u**4 . 2 2 // u [pop] [Fw] while popopd + u**4 2 . 2 // u [pop] [Fw] while popopd + u**4 2 2 . // u [pop] [Fw] while popopd + u**4 1 . u [pop] [Fw] while popopd + u**4 1 u . [pop] [Fw] while popopd + u**4 1 u [pop] . [Fw] while popopd + u**4 1 u [pop] [Fw] . while popopd + u**4 1 u . Fw [pop] [Fw] while popopd + u**4 1 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd + u**4 1 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd + u**4 1 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd + u**4 1 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd + u**4 1 u . Ft Fb [pop] [Fw] while popopd + u**4 1 u . [over] dip * Fb [pop] [Fw] while popopd + u**4 1 u [over] . dip * Fb [pop] [Fw] while popopd + u**4 1 . over u * Fb [pop] [Fw] while popopd + u**4 1 u**4 . u * Fb [pop] [Fw] while popopd + u**4 1 u**4 u . * Fb [pop] [Fw] while popopd + u**4 1 u**5 . Fb [pop] [Fw] while popopd + u**4 1 u**5 . [[sqr] dip 2 //] dip [pop] [Fw] while popopd + u**4 1 u**5 [[sqr] dip 2 //] . dip [pop] [Fw] while popopd + u**4 1 . [sqr] dip 2 // u**5 [pop] [Fw] while popopd + u**4 1 [sqr] . dip 2 // u**5 [pop] [Fw] while popopd + u**4 . sqr 1 2 // u**5 [pop] [Fw] while popopd + u**4 . dup mul 1 2 // u**5 [pop] [Fw] while popopd + u**4 u**4 . mul 1 2 // u**5 [pop] [Fw] while popopd + u**8 . 1 2 // u**5 [pop] [Fw] while popopd + u**8 1 . 2 // u**5 [pop] [Fw] while popopd + u**8 1 2 . // u**5 [pop] [Fw] while popopd + u**8 0 . u**5 [pop] [Fw] while popopd + u**8 0 u**5 . [pop] [Fw] while popopd + u**8 0 u**5 [pop] . [Fw] while popopd + u**8 0 u**5 [pop] [Fw] . while popopd + u**8 0 u**5 . popopd + u**5 . + + + +```python +result +``` + + + + +$$u^{5}$$ + + + +Let's try partial evaluation by hand and use a "stronger" thunk. + +Caret underscoring indicates terms that form thunks. When an arg is unavailable for a computation we just postpone it until the arg becomes available and in the meantime treat the pending computation as one unit. + + u 5 . F + u 5 . 1 [pop] [Fw] while popopd + u 5 1 . [pop] [Fw] while popopd + u 5 1 [pop] . [Fw] while popopd + u 5 1 [pop] [Fw] . while popopd + u 5 1 . Fw [pop] [Fw] while popopd + u 5 1 . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd + u 5 1 [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd + u 5 1 [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd + u 5 1 [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd + u 5 1 . Ft Fb [pop] [Fw] while popopd + u 5 1 . [over] dip * Fb [pop] [Fw] while popopd + u 5 1 [over] . dip * Fb [pop] [Fw] while popopd + u 5 . over 1 * Fb [pop] [Fw] while popopd + u 5 u . 1 * Fb [pop] [Fw] while popopd + u 5 u 1 . * Fb [pop] [Fw] while popopd + u 5 u . Fb [pop] [Fw] while popopd + u 5 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd + u 5 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd + u 5 . [sqr] dip 2 // u [pop] [Fw] while popopd + u 5 [sqr] . dip 2 // u [pop] [Fw] while popopd + u . sqr 5 2 // u [pop] [Fw] while popopd + u . dup mul 5 2 // u [pop] [Fw] while popopd + u dup * . 5 2 // u [pop] [Fw] while popopd + ^^^^^^^ + + + u dup * 2 u [pop] [Fw] . while popopd + u dup * 2 u . Fw [pop] [Fw] while popopd + u dup * 2 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd + u dup * 2 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd + u dup * 2 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd + u dup * 2 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd + u dup * 2 u . Fb [pop] [Fw] while popopd + u dup * 2 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd + u dup * 2 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd + u dup * 2 . [sqr] dip 2 // u [pop] [Fw] while popopd + u dup * 2 [sqr] . dip 2 // u [pop] [Fw] while popopd + u dup * . sqr 2 2 // u [pop] [Fw] while popopd + u dup * . dup mul 2 2 // u [pop] [Fw] while popopd + u dup * dup * . 2 2 // u [pop] [Fw] while popopd + ^^^^^^^^^^^^^ + + + + +w/ `K == u dup * dup *` + + K 1 u [pop] [Fw] . while popopd + K 1 u . Fw [pop] [Fw] while popopd + K 1 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd + K 1 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd + K 1 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd + K 1 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd + K 1 u . Ft Fb [pop] [Fw] while popopd + K 1 u . [over] dip * Fb [pop] [Fw] while popopd + K 1 u [over] . dip * Fb [pop] [Fw] while popopd + K 1 . over u * Fb [pop] [Fw] while popopd + K 1 K . u * Fb [pop] [Fw] while popopd + K 1 K u . * Fb [pop] [Fw] while popopd + K 1 K u * . Fb [pop] [Fw] while popopd + ^^^^^ + +w/ `L == K u *` + + K 1 L . Fb [pop] [Fw] while popopd + K 1 L . [[sqr] dip 2 //] dip [pop] [Fw] while popopd + K 1 L [[sqr] dip 2 //] . dip [pop] [Fw] while popopd + K 1 . [sqr] dip 2 // L [pop] [Fw] while popopd + K 1 [sqr] . dip 2 // L [pop] [Fw] while popopd + K . sqr 1 2 // L [pop] [Fw] while popopd + K . dup mul 1 2 // L [pop] [Fw] while popopd + K K . mul 1 2 // L [pop] [Fw] while popopd + K K * . 1 2 // L [pop] [Fw] while popopd + ^^^^^ + K K * . 1 2 // L [pop] [Fw] while popopd + K K * 1 . 2 // L [pop] [Fw] while popopd + K K * 1 2 . // L [pop] [Fw] while popopd + K K * 0 . L [pop] [Fw] while popopd + K K * 0 L . [pop] [Fw] while popopd + K K * 0 L [pop] . [Fw] while popopd + K K * 0 L [pop] [Fw] . while popopd + ^^^^^ + K K * 0 L . popopd + L . + +So: + + K == u dup * dup * + L == K u * + +Our result "thunk" would be: + + u dup * dup * u * + +Mechanically, you could do: + + u dup * dup * u * + u u [dup * dup *] dip * + u dup [dup * dup *] dip * + + + F5 == dup [dup * dup *] dip * + +But we can swap the two arguments to the final `*` to get all mentions of `u` to the left: + + + u u dup * dup * * + + +Then de-duplicate "u": + + + u dup dup * dup * * + + +To arrive at a startlingly elegant form for F5: + + + F5 == dup dup * dup * * + + +```python +stack = list_to_stack([sympy.symbols('u')]) +viewer = TracePrinter() +try: + (result, _) = run('dup dup * dup * *', stack, D, viewer.viewer)[0] +except Exception, e: + print e +viewer.print_() +result +``` + + u . dup dup * dup * * + u u . dup * dup * * + u u u . * dup * * + u u**2 . dup * * + u u**2 u**2 . * * + u u**4 . * + u**5 . + + + + + +$$u^{5}$$ + + + +I'm not sure how to implement these kinds of thunks. I think you have to have support in the interpreter, or you have to modify all of the functions like `dup` to check for thunks in their inputs. + +Working on the compiler, from this: + + dup dup * dup * * + +We can already generate: + + --------------------------------- + (a0, stack) = stack + a1 = mul(a0, a0) + a2 = mul(a1, a1) + a3 = mul(a2, a0) + stack = (a3, stack) + --------------------------------- + +This is pretty old stuff... (E.g. from 1999, M. Anton Ertl [Compilation of Stack-Based Languages](http://www.complang.tuwien.ac.at/projects/rafts.html) he goes a lot further for Forth.) + +## "A Transformation Based Approach to Semantics-Directed Code Generation" + +by Arthur Nunes-Harwitt + +https://dl.acm.org/citation.cfm?doid=2635648.2635652 + + + + +```python +def m(x, y): return x * y + +print m(2, 3) +``` + + 6 + + + +```python +def m(x): return lambda y: x * y + +print m(2)(3) +``` + + 6 + + + +```python +def m(x): return "lambda y: %(x)s * y" % locals() + +print m(2) +print eval(m(2))(3) +``` + + lambda y: 2 * y + 6 + + +In Joy: + + m == [*] cons + + 3 2 m i + 3 2 [*] cons i + 3 [2 *] i + 3 2 * + 6 + + + +```python +def p(n, b): # original + return 1 if n == 0 else b * p(n - 1, b) + + +def p(n): # curried + return lambda b: 1 if n == 0 else b * p(n - 1, b) + + +def p(n): # quoted + return "lambda b: 1 if %(n)s == 0 else b * p(%(n)s - 1, b)" % locals() + + +print p(3) +``` + + lambda b: 1 if 3 == 0 else b * p(3 - 1, b) + + +Original + + p == [0 =] [popop 1] [-- over] [dip *] genrec + + b n p + b n [0 =] [popop 1] [-- over [p] dip *] + + b n -- over [p] dip * + b n-1 over [p] dip * + b n-1 b [p] dip * + b n-1 p b * + + +curried, quoted + + n p + --------------------------------------------- + [[n 0 =] [pop 1] [dup n --] [*] genrec] + + +```python +def p(n): # lambda lowered + return ( + lambda b: 1 + if n == 0 else + lambda b: b * p(n - 1, b) + ) + + +def p(n): # lambda lowered quoted + return ( + "lambda b: 1" + if n == 0 else + "lambda b: b * p(%(n)s - 1, b)" % locals() + ) + +print p(3) +``` + + lambda b: b * p(3 - 1, b) + + + + p == [0 =] [[pop 1]] [ [-- [dup] dip p *] cons ]ifte + + + 3 p + 3 [-- [dup] dip p *] cons + [3 -- [dup] dip p *] + + + +```python +def p(n): # expression lifted + if n == 0: + return lambda b: 1 + f = p(n - 1) + return lambda b: b * f(b) + + +print p(3)(2) +``` + + 8 + + + +```python +def p(n): # quoted + if n == 0: + return "lambda b: 1" + f = p(n - 1) + return "lambda b: b * (%(f)s)(b)" % locals() + +print p(3) +print eval(p(3))(2) +``` + + lambda b: b * (lambda b: b * (lambda b: b * (lambda b: 1)(b))(b))(b) + 8 + + + p == [0 =] [pop [pop 1]] [-- p [dupdip *] cons] ifte + + + 3 p + 3 -- p [dupdip *] cons + 2 p [dupdip *] cons + 2 -- p [dupdip *] cons [dupdip *] cons + 1 p [dupdip *] cons [dupdip *] cons + 1 -- p [dupdip *] cons [dupdip *] cons [dupdip *] cons + 0 p [dupdip *] cons [dupdip *] cons [dupdip *] cons + 0 pop [pop 1] [dupdip *] cons [dupdip *] cons [dupdip *] cons + [pop 1] [dupdip *] cons [dupdip *] cons [dupdip *] cons + ... + [[[[pop 1] dupdip *] dupdip *] dupdip *] + + + 2 [[[[pop 1] dupdip *] dupdip *] dupdip *] i + 2 [[[pop 1] dupdip *] dupdip *] dupdip * + 2 [[pop 1] dupdip *] dupdip * 2 * + 2 [pop 1] dupdip * 2 * 2 * + 2 pop 1 2 * 2 * 2 * + 1 2 * 2 * 2 * + + + + p == [0 =] [pop [pop 1]] [-- p [dupdip *] cons] ifte + p == [0 =] [pop [pop 1]] [-- [p] i [dupdip *] cons] ifte + p == [0 =] [pop [pop 1]] [--] [i [dupdip *] cons] genrec + + +```python +define('p == [0 =] [pop [pop 1]] [--] [i [dupdip *] cons] genrec') +``` + + +```python +J('3 p') +``` + + [[[[pop 1] dupdip *] dupdip *] dupdip *] + + + +```python +V('2 [[[[pop 1] dupdip *] dupdip *] dupdip *] i') +``` + + . 2 [[[[pop 1] dupdip *] dupdip *] dupdip *] i + 2 . [[[[pop 1] dupdip *] dupdip *] dupdip *] i + 2 [[[[pop 1] dupdip *] dupdip *] dupdip *] . i + 2 . [[[pop 1] dupdip *] dupdip *] dupdip * + 2 [[[pop 1] dupdip *] dupdip *] . dupdip * + 2 . [[pop 1] dupdip *] dupdip * 2 * + 2 [[pop 1] dupdip *] . dupdip * 2 * + 2 . [pop 1] dupdip * 2 * 2 * + 2 [pop 1] . dupdip * 2 * 2 * + 2 . pop 1 2 * 2 * 2 * + . 1 2 * 2 * 2 * + 1 . 2 * 2 * 2 * + 1 2 . * 2 * 2 * + 2 . 2 * 2 * + 2 2 . * 2 * + 4 . 2 * + 4 2 . * + 8 . + + + +```python +stack = list_to_stack([sympy.symbols('u')]) +(result, s) = run('p i', stack, D)[0] +result +``` + +From this: + + p == [0 =] [pop pop 1] [-- over] [dip *] genrec + +To this: + + p == [0 =] [pop [pop 1]] [--] [i [dupdip *] cons] genrec + + + + +## Try it with `F()`: + + +```python +def odd(n): return n % 2 + + +def F(u, k): + z = 1 + while k != 0: + if odd(k): + z = z * u + k = k / 2 + u = u * u + return z + +F(2, 5) +``` + + +```python +def F(k): + def _F(u, k=k): + z = 1 + while k != 0: + if odd(k): + z = z * u + k = k / 2 + u = u * u + return z + return _F + +F(5)(2) +``` + + +```python +def F(k): + def _F(u, k=k): + if k == 0: + z = 1 + else: + z = F(k / 2)(u) + z *= z + if odd(k): + z = z * u + return z + return _F + +F(5)(2) +``` + + +```python +def F(k): + if k == 0: + z = lambda u: 1 + else: + f = F(k / 2) + def z(u): + uu = f(u) + uu *= uu + return uu * u if odd(k) else uu + return z + +F(5)(2) +``` + + +```python +def F(k): + if k == 0: + z = lambda u: 1 + else: + f = F(k / 2) + if odd(k): + z = lambda u: (lambda fu, u: fu * fu * u)(f(u), u) + else: + z = lambda u: (lambda fu, u: fu * fu)(f(u), u) + return z + +F(5)(2) +``` + + +```python +def F(k): + if k == 0: + z = "lambda u: 1" + else: + f = F(k / 2) + if odd(k): + z = "lambda u: (lambda fu, u: fu * fu * u)((%(f)s)(u), u)" % locals() + else: + z = "lambda u: (lambda fu, u: fu * fu)((%(f)s)(u), u)" % locals() + return z + +source = F(5) +print source +eval(source)(2) +``` + +Hmm... + + +```python +for n in range(4): + print F(n) +``` + + +```python +def F(k): + if k == 0: + z = "lambda u: 1" + elif k == 1: + z = "lambda u: u" + else: + f = F(k / 2) + if odd(k): + z = "lambda u: (lambda fu, u: fu * fu * u)((%(f)s)(u), u)" % locals() + else: + z = "lambda u: (lambda fu, u: fu * fu)((%(f)s)(u), u)" % locals() + return z + +source = F(5) +print source +eval(source)(2) +``` + + +```python +for n in range(4): + print F(n) +``` + + +```python +def F(k): + if k == 0: + z = "lambda u: 1" + elif k == 1: + z = "lambda u: u" + else: + m = k / 2 + if odd(k): + if m == 0: + z = "lambda u: 1" + elif m == 1: + z = "lambda u: u * u * u" + else: + z = "lambda u: (lambda fu, u: fu * fu * u)((%s)(u), u)" % F(m) + else: + if m == 0: + z = "lambda u: 1" + elif m == 1: + z = "lambda u: u * u" + else: + z = "lambda u: (lambda u: u * u)((%s)(u))" % F(m) + return z + +source = F(5) +print source +eval(source)(2) +``` + + +```python +def F(k): + if k == 0: + z = "lambda u: 1" + elif k == 1: + z = "lambda u: u" + else: + m = k / 2 + if m == 0: + z = "lambda u: 1" + + elif odd(k): + if m == 1: + z = "lambda u: u * u * u" + else: + z = "lambda u: (lambda fu, u: fu * fu * u)((%s)(u), u)" % F(m) + else: + if m == 1: + z = "lambda u: u * u" + else: + z = "lambda u: (lambda u: u * u)((%s)(u))" % F(m) + return z + +source = F(5) +print source +eval(source)(2) +``` + + +```python +for n in range(7): + source = F(n) + print n, '%2i' % eval(source)(2), source +``` + +So that gets pretty good, eh? + + +But looking back at the definition in Joy, it doesn't seem easy to directly apply this technique to Joy code: + + Ft == [over] dip * + Fb == [[sqr] dip 2 //] dip + Fw == [pop odd] [Ft] [] ifte Fb + F == 1 [pop] [Fw] while popopd + + +But a direct translation of the Python code..? + + + F == [ + [[0 =] [pop 1]] + [[1 =] []] + [_F.0] + ] cond + + _F.0 == dup 2 // [ + [[0 =] [pop 1]] + [[pop odd] _F.1] + [_F.2] + ] cond + + _F.1 == [1 =] [pop [dup dup * *]] [popd F [dupdip over * *] cons] ifte + _F.2 == [1 =] [pop [dup *]] [popd F [i dup *] cons] ifte + + +Try it: + + 5 F + 5 [ [[0 =] [pop 1]] [[1 =] []] [_F.0] ] cond + 5 _F.0 + 5 dup 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond + 5 5 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond + + 5 2 [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond + 5 2 _F.1 + 5 2 [1 =] [popop [dup dup * *]] [popd F [dupdip over * *] cons] ifte + 5 2 popd F [dupdip over * *] cons + 2 F [dupdip over * *] cons + + 2 F [dupdip over * *] cons + + 2 F + 2 [ [[0 =] [pop 1]] [[1 =] []] [_F.0] ] cond + 2 _F.0 + 2 dup 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond + 2 2 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond + 2 1 [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond + 2 1 _F.2 + 2 1 [1 =] [popop [dup *]] [popd F [i dup *] cons] ifte + 2 1 popop [dup *] + [dup *] + + + 2 F [dupdip over * *] cons + [dup *] [dupdip over * *] cons + [[dup *] dupdip over * *] + +And here it is in action: + + 2 [[dup *] dupdip over * *] i + 2 [dup *] dupdip over * * + 2 dup * 2 over * * + 2 2 * 2 over * * + 4 2 over * * + 4 2 4 * * + 4 8 * + 32 + + + +So, it works, but in this case the results of the partial evaluation are more elegant. + + + + + + +## Try it with `hylomorphism()`: + + +```python +def hylomorphism(c, F, P, G): + '''Return a hylomorphism function H.''' + + def H(a): + if P(a): + result = c + else: + b, aa = G(a) + result = F(b, H(aa)) + return result + + return H +``` + +### First, curry + +With abuse of syntax: + + +```python +def hylomorphism(c): + return lambda F: lambda P: lambda G: lambda a: ( + if P(a): + result = c + else: + b, aa = G(a) + result = F(b)(H(aa)) + return result + ) + +``` + +### lambda lowering + + + +```python +def hylomorphism(c): + def r(a): + def rr(P): + if P(a): + return lambda F: lambda G: c + return lambda F: lambda G: ( + b, aa = G(a) + return F(b)(H(aa)) + ) + return rr + return r + + +``` + +### expression lifting + + + +```python +def hylomorphism(c): + def r(a): + def rr(P): + def rrr(G): + if P(a): + return lambda F: c + b, aa = G(a) + H = hylomorphism(c)(aa)(P)(G) + return lambda F: F(b)(H(F)) + return rrr + return rr + return r + + +``` + +### quoted + + + +```python +def hylomorphism(c): + def r(a): + def rr(P): + def rrr(G): + if P(a): + return "lambda F: %s" % (c,) + b, aa = G(a) + H = hylomorphism(c)(aa)(P)(G) + return "lambda F: F(%(b)s)((%(H)s)(F))" % locals() + return rrr + return rr + return r + + +``` + + +```python +hylomorphism(0)(3)(lambda n: n == 0)(lambda n: (n-1, n-1)) +``` + + +```python +def F(a): + def _F(b): + print a, b + return a + b + return _F + +``` + + +```python +F(2)(3) +``` + + +```python +eval(hylomorphism(0)(5)(lambda n: n == 0)(lambda n: (n-1, n-1)))(F) +``` + + +```python +eval(hylomorphism([])(5)(lambda n: n == 0)(lambda n: (n-1, n-1)))(lambda a: lambda b: [a] + b) +``` + + +```python +hylomorphism(0)([1, 2, 3])(lambda n: not n)(lambda n: (n[0], n[1:])) +``` + + +```python +hylomorphism([])([1, 2, 3])(lambda n: not n)(lambda n: (n[1:], n[1:])) +``` + + +```python +eval(hylomorphism([])([1, 2, 3])(lambda n: not n)(lambda n: (n[1:], n[1:])))(lambda a: lambda b: [a] + b) +``` + + +```python +def hylomorphism(c): + return lambda a: lambda P: ( + if P(a): + result = lambda F: lambda G: c + else: + result = lambda F: lambda G: ( + b, aa = G(a) + return F(b)(H(aa)) + ) + return result + ) + +``` + + +```python +def hylomorphism(c): + return lambda a: ( + lambda F: lambda P: lambda G: c + if P(a) else + lambda F: lambda P: lambda G: ( + b, aa = G(a) + return F(b)(H(aa)) + ) + ) + +``` + + +```python +def hylomorphism(c): + return lambda a: lambda G: ( + lambda F: lambda P: c + if P(a) else + b, aa = G(a) + lambda F: lambda P: F(b)(H(aa)) + ) + +``` diff --git a/docs/with_sympy.rst b/docs/with_sympy.rst new file mode 100644 index 0000000..e4d67d0 --- /dev/null +++ b/docs/with_sympy.rst @@ -0,0 +1,1313 @@ + +Symbolic Evaluation with SymPy +============================== + +.. code:: ipython2 + + import sympy + + from joy.joy import run + from joy.library import UnaryBuiltinWrapper + from joy.utils.pretty_print import TracePrinter + from joy.utils.stack import list_to_stack + + from notebook_preamble import D, J, V, define + +.. code:: ipython2 + + sympy.init_printing() + +`SypPy Symbols `__ +------------------------------------------------------------------------------------------- + +The SymPy package provides a powerful and elegant +`"thunk" `__ object that can take +the place of a numeric value in calculations and "record" the operations +performed on it. + +We can create some of these objects and put them on the Joy stack: + +.. code:: ipython2 + + stack = list_to_stack(sympy.symbols('c a b')) + +If we evaluate the ``quadratic`` program + +:: + + over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2 + +The `SypPy +Symbols `__ +will become the symbolic expression of the math operations. +Unfortunately, the library ``sqrt`` function doesn't work with the SymPy +objects: + +.. code:: ipython2 + + viewer = TracePrinter() + try: + run('over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2', stack, D, viewer.viewer) + except Exception, e: + print e + viewer.print_() + + +.. parsed-literal:: + + can't convert expression to float + b a c . over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2 + b a c a . [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2 + b a c a [[[neg] dupdip sqr 4] dipd * * - sqrt pm] . dip 2 * [/] cons app2 + b a c . [[neg] dupdip sqr 4] dipd * * - sqrt pm a 2 * [/] cons app2 + b a c [[neg] dupdip sqr 4] . dipd * * - sqrt pm a 2 * [/] cons app2 + b . [neg] dupdip sqr 4 a c * * - sqrt pm a 2 * [/] cons app2 + b [neg] . dupdip sqr 4 a c * * - sqrt pm a 2 * [/] cons app2 + b . neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2 + -b . b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2 + -b b . sqr 4 a c * * - sqrt pm a 2 * [/] cons app2 + -b b . dup mul 4 a c * * - sqrt pm a 2 * [/] cons app2 + -b b b . mul 4 a c * * - sqrt pm a 2 * [/] cons app2 + -b b**2 . 4 a c * * - sqrt pm a 2 * [/] cons app2 + -b b**2 4 . a c * * - sqrt pm a 2 * [/] cons app2 + -b b**2 4 a . c * * - sqrt pm a 2 * [/] cons app2 + -b b**2 4 a c . * * - sqrt pm a 2 * [/] cons app2 + -b b**2 4 a*c . * - sqrt pm a 2 * [/] cons app2 + -b b**2 4*a*c . - sqrt pm a 2 * [/] cons app2 + -b -4*a*c + b**2 . sqrt pm a 2 * [/] cons app2 + + +We can pick out that first symbolic expression obect from the Joy stack: + +.. code:: ipython2 + + S, E = viewer.history[-1] + +.. code:: ipython2 + + q = S[0] + q + + + + +.. math:: + + - 4 a c + b^{2} + + + +The Python ``math.sqrt()`` function causes the "can't convert expression +to float" exception but ``sympy.sqrt()`` does not: + +.. code:: ipython2 + + sympy.sqrt(q) + + + + +.. math:: + + \sqrt{- 4 a c + b^{2}} + + + +Use ``sympy.sqrt`` +------------------ + +This is easy to fix. + +.. code:: ipython2 + + D['sqrt'] = UnaryBuiltinWrapper(sympy.sqrt) + +Now it works just fine. + +.. code:: ipython2 + + (root1, (root2, _)) = run('over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2', stack, D)[0] + +.. code:: ipython2 + + root1 + + + + +.. math:: + + \frac{1}{2 a} \left(- b - \sqrt{- 4 a c + b^{2}}\right) + + + +.. code:: ipython2 + + root2 + + + + +.. math:: + + \frac{1}{2 a} \left(- b + \sqrt{- 4 a c + b^{2}}\right) + + + +At some point I will probably make an optional library of Joy wrappers +for SymPy functions, and either load it automatically if SymPy +installation is available or have a CLI switch or something. There's a +huge amount of incredibly useful stuff and I don't see why Joy shouldn't +expose another interface for using it. (As an example, the symbolic +expressions can be "lambdafied" into very fast versions, i.e. a function +that takes ``a``, ``b``, and ``c`` and computes the value of the root +using just low-level fast code, bypassing Joy and Python. Also, Numpy, +&c.) + +Partial Evaluation +------------------ + +`Futamura +projections `__ + +Starting with the example from `Partial Computation of +Programs `__ by `Yoshihiko +Futamura `__ of a function to compute ``u`` to the +``k``\ th power: + +.. code:: ipython2 + + def F(u, k): + z = 1 + while k != 0: + if odd(k): + z = z * u + k = k / 2 + u = u * u + return z + +Partial evaluation with ``k = 5``: + +.. code:: ipython2 + + def F5(u): + z = 1 * u + u = u * u + u = u * u + z = z * u + return z + +Translate ``F(u, k)`` to Joy + +:: + + u k 1 # z = 1 + [pop] [Fw] while # the while statement + popopd # discard u k, "return" z + +What's Fw? + +:: + + u k z [pop odd] [Ft] [] ifte # the if statement + [2 //] dip # k = k / 2 floordiv + [sqr] dipd # u = u * u + + [[sqr] dip 2 //] dip # We can merge last two lines. + +Helper function Ft (to compute z = z \* u). + +:: + + u k z Ft + --------------- + u k u*z + + + Ft == [over] dip * + +Putting it together: + +:: + + Ft == [over] dip * + Fb == [[sqr] dip 2 //] dip + Fw == [pop odd] [Ft] [] ifte Fb + F == 1 [pop] [Fw] while popopd + +.. code:: ipython2 + + define('odd == 2 %') + +.. code:: ipython2 + + define('Ft == [over] dip *') + +.. code:: ipython2 + + define('Fb == [[sqr] dip 2 //] dip') + +.. code:: ipython2 + + define('Fw == [pop odd] [Ft] [] ifte Fb') + +.. code:: ipython2 + + define('F == 1 [pop] [Fw] while popopd') + +Try it out: + +.. code:: ipython2 + + J('2 5 F') + + +.. parsed-literal:: + + 32 + + +In order to elide the tests let's define special versions of ``while`` +and ``ifte``: + +.. code:: ipython2 + + from joy.joy import joy + from joy.library import FunctionWrapper + from joy.parser import Symbol + from joy.utils.stack import concat + + + S_while = Symbol('while') + + + @FunctionWrapper + def while_(S, expression, dictionary): + '''[if] [body] while''' + (body, (if_, stack)) = S + if joy(stack, if_, dictionary)[0][0]: + expression = concat(body, (if_, (body, (S_while, expression)))) + return stack, expression, dictionary + + + @FunctionWrapper + def ifte(stack, expression, dictionary): + '''[if] [then] [else] ifte''' + (else_, (then, (if_, stack))) = stack + if_res = joy(stack, if_, dictionary)[0][0] + quote = then if if_res else else_ + expression = concat(quote, expression) + return (stack, expression, dictionary) + + + D['ifte'] = ifte + D['while'] = while_ + +And with a SymPy symbol for the ``u`` argument: + +.. code:: ipython2 + + stack = list_to_stack([5, sympy.symbols('u')]) + viewer = TracePrinter() + try: + (result, _) = run('F', stack, D, viewer.viewer)[0] + except Exception, e: + print e + viewer.print_() + + +.. parsed-literal:: + + u 5 . F + u 5 . 1 [pop] [Fw] while popopd + u 5 1 . [pop] [Fw] while popopd + u 5 1 [pop] . [Fw] while popopd + u 5 1 [pop] [Fw] . while popopd + u 5 1 . Fw [pop] [Fw] while popopd + u 5 1 . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd + u 5 1 [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd + u 5 1 [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd + u 5 1 [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd + u 5 1 . Ft Fb [pop] [Fw] while popopd + u 5 1 . [over] dip * Fb [pop] [Fw] while popopd + u 5 1 [over] . dip * Fb [pop] [Fw] while popopd + u 5 . over 1 * Fb [pop] [Fw] while popopd + u 5 u . 1 * Fb [pop] [Fw] while popopd + u 5 u 1 . * Fb [pop] [Fw] while popopd + u 5 u . Fb [pop] [Fw] while popopd + u 5 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd + u 5 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd + u 5 . [sqr] dip 2 // u [pop] [Fw] while popopd + u 5 [sqr] . dip 2 // u [pop] [Fw] while popopd + u . sqr 5 2 // u [pop] [Fw] while popopd + u . dup mul 5 2 // u [pop] [Fw] while popopd + u u . mul 5 2 // u [pop] [Fw] while popopd + u**2 . 5 2 // u [pop] [Fw] while popopd + u**2 5 . 2 // u [pop] [Fw] while popopd + u**2 5 2 . // u [pop] [Fw] while popopd + u**2 2 . u [pop] [Fw] while popopd + u**2 2 u . [pop] [Fw] while popopd + u**2 2 u [pop] . [Fw] while popopd + u**2 2 u [pop] [Fw] . while popopd + u**2 2 u . Fw [pop] [Fw] while popopd + u**2 2 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd + u**2 2 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd + u**2 2 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd + u**2 2 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd + u**2 2 u . Fb [pop] [Fw] while popopd + u**2 2 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd + u**2 2 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd + u**2 2 . [sqr] dip 2 // u [pop] [Fw] while popopd + u**2 2 [sqr] . dip 2 // u [pop] [Fw] while popopd + u**2 . sqr 2 2 // u [pop] [Fw] while popopd + u**2 . dup mul 2 2 // u [pop] [Fw] while popopd + u**2 u**2 . mul 2 2 // u [pop] [Fw] while popopd + u**4 . 2 2 // u [pop] [Fw] while popopd + u**4 2 . 2 // u [pop] [Fw] while popopd + u**4 2 2 . // u [pop] [Fw] while popopd + u**4 1 . u [pop] [Fw] while popopd + u**4 1 u . [pop] [Fw] while popopd + u**4 1 u [pop] . [Fw] while popopd + u**4 1 u [pop] [Fw] . while popopd + u**4 1 u . Fw [pop] [Fw] while popopd + u**4 1 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd + u**4 1 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd + u**4 1 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd + u**4 1 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd + u**4 1 u . Ft Fb [pop] [Fw] while popopd + u**4 1 u . [over] dip * Fb [pop] [Fw] while popopd + u**4 1 u [over] . dip * Fb [pop] [Fw] while popopd + u**4 1 . over u * Fb [pop] [Fw] while popopd + u**4 1 u**4 . u * Fb [pop] [Fw] while popopd + u**4 1 u**4 u . * Fb [pop] [Fw] while popopd + u**4 1 u**5 . Fb [pop] [Fw] while popopd + u**4 1 u**5 . [[sqr] dip 2 //] dip [pop] [Fw] while popopd + u**4 1 u**5 [[sqr] dip 2 //] . dip [pop] [Fw] while popopd + u**4 1 . [sqr] dip 2 // u**5 [pop] [Fw] while popopd + u**4 1 [sqr] . dip 2 // u**5 [pop] [Fw] while popopd + u**4 . sqr 1 2 // u**5 [pop] [Fw] while popopd + u**4 . dup mul 1 2 // u**5 [pop] [Fw] while popopd + u**4 u**4 . mul 1 2 // u**5 [pop] [Fw] while popopd + u**8 . 1 2 // u**5 [pop] [Fw] while popopd + u**8 1 . 2 // u**5 [pop] [Fw] while popopd + u**8 1 2 . // u**5 [pop] [Fw] while popopd + u**8 0 . u**5 [pop] [Fw] while popopd + u**8 0 u**5 . [pop] [Fw] while popopd + u**8 0 u**5 [pop] . [Fw] while popopd + u**8 0 u**5 [pop] [Fw] . while popopd + u**8 0 u**5 . popopd + u**5 . + + +.. code:: ipython2 + + result + + + + +.. math:: + + u^{5} + + + +Let's try partial evaluation by hand and use a "stronger" thunk. + +Caret underscoring indicates terms that form thunks. When an arg is +unavailable for a computation we just postpone it until the arg becomes +available and in the meantime treat the pending computation as one unit. + +:: + + u 5 . F + u 5 . 1 [pop] [Fw] while popopd + u 5 1 . [pop] [Fw] while popopd + u 5 1 [pop] . [Fw] while popopd + u 5 1 [pop] [Fw] . while popopd + u 5 1 . Fw [pop] [Fw] while popopd + u 5 1 . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd + u 5 1 [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd + u 5 1 [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd + u 5 1 [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd + u 5 1 . Ft Fb [pop] [Fw] while popopd + u 5 1 . [over] dip * Fb [pop] [Fw] while popopd + u 5 1 [over] . dip * Fb [pop] [Fw] while popopd + u 5 . over 1 * Fb [pop] [Fw] while popopd + u 5 u . 1 * Fb [pop] [Fw] while popopd + u 5 u 1 . * Fb [pop] [Fw] while popopd + u 5 u . Fb [pop] [Fw] while popopd + u 5 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd + u 5 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd + u 5 . [sqr] dip 2 // u [pop] [Fw] while popopd + u 5 [sqr] . dip 2 // u [pop] [Fw] while popopd + u . sqr 5 2 // u [pop] [Fw] while popopd + u . dup mul 5 2 // u [pop] [Fw] while popopd + u dup * . 5 2 // u [pop] [Fw] while popopd + ^^^^^^^ + +:: + + u dup * 2 u [pop] [Fw] . while popopd + u dup * 2 u . Fw [pop] [Fw] while popopd + u dup * 2 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd + u dup * 2 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd + u dup * 2 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd + u dup * 2 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd + u dup * 2 u . Fb [pop] [Fw] while popopd + u dup * 2 u . [[sqr] dip 2 //] dip [pop] [Fw] while popopd + u dup * 2 u [[sqr] dip 2 //] . dip [pop] [Fw] while popopd + u dup * 2 . [sqr] dip 2 // u [pop] [Fw] while popopd + u dup * 2 [sqr] . dip 2 // u [pop] [Fw] while popopd + u dup * . sqr 2 2 // u [pop] [Fw] while popopd + u dup * . dup mul 2 2 // u [pop] [Fw] while popopd + u dup * dup * . 2 2 // u [pop] [Fw] while popopd + ^^^^^^^^^^^^^ + +w/ ``K == u dup * dup *`` + +:: + + K 1 u [pop] [Fw] . while popopd + K 1 u . Fw [pop] [Fw] while popopd + K 1 u . [pop odd] [Ft] [] ifte Fb [pop] [Fw] while popopd + K 1 u [pop odd] . [Ft] [] ifte Fb [pop] [Fw] while popopd + K 1 u [pop odd] [Ft] . [] ifte Fb [pop] [Fw] while popopd + K 1 u [pop odd] [Ft] [] . ifte Fb [pop] [Fw] while popopd + K 1 u . Ft Fb [pop] [Fw] while popopd + K 1 u . [over] dip * Fb [pop] [Fw] while popopd + K 1 u [over] . dip * Fb [pop] [Fw] while popopd + K 1 . over u * Fb [pop] [Fw] while popopd + K 1 K . u * Fb [pop] [Fw] while popopd + K 1 K u . * Fb [pop] [Fw] while popopd + K 1 K u * . Fb [pop] [Fw] while popopd + ^^^^^ + +w/ ``L == K u *`` + +:: + + K 1 L . Fb [pop] [Fw] while popopd + K 1 L . [[sqr] dip 2 //] dip [pop] [Fw] while popopd + K 1 L [[sqr] dip 2 //] . dip [pop] [Fw] while popopd + K 1 . [sqr] dip 2 // L [pop] [Fw] while popopd + K 1 [sqr] . dip 2 // L [pop] [Fw] while popopd + K . sqr 1 2 // L [pop] [Fw] while popopd + K . dup mul 1 2 // L [pop] [Fw] while popopd + K K . mul 1 2 // L [pop] [Fw] while popopd + K K * . 1 2 // L [pop] [Fw] while popopd + ^^^^^ + K K * . 1 2 // L [pop] [Fw] while popopd + K K * 1 . 2 // L [pop] [Fw] while popopd + K K * 1 2 . // L [pop] [Fw] while popopd + K K * 0 . L [pop] [Fw] while popopd + K K * 0 L . [pop] [Fw] while popopd + K K * 0 L [pop] . [Fw] while popopd + K K * 0 L [pop] [Fw] . while popopd + ^^^^^ + K K * 0 L . popopd + L . + +So: + +:: + + K == u dup * dup * + L == K u * + +Our result "thunk" would be: + +:: + + u dup * dup * u * + +Mechanically, you could do: + +:: + + u dup * dup * u * + u u [dup * dup *] dip * + u dup [dup * dup *] dip * + + + F5 == dup [dup * dup *] dip * + +But we can swap the two arguments to the final ``*`` to get all mentions +of ``u`` to the left: + +:: + + u u dup * dup * * + +Then de-duplicate "u": + +:: + + u dup dup * dup * * + +To arrive at a startlingly elegant form for F5: + +:: + + F5 == dup dup * dup * * + +.. code:: ipython2 + + stack = list_to_stack([sympy.symbols('u')]) + viewer = TracePrinter() + try: + (result, _) = run('dup dup * dup * *', stack, D, viewer.viewer)[0] + except Exception, e: + print e + viewer.print_() + result + + +.. parsed-literal:: + + u . dup dup * dup * * + u u . dup * dup * * + u u u . * dup * * + u u**2 . dup * * + u u**2 u**2 . * * + u u**4 . * + u**5 . + + + + +.. math:: + + u^{5} + + + +I'm not sure how to implement these kinds of thunks. I think you have to +have support in the interpreter, or you have to modify all of the +functions like ``dup`` to check for thunks in their inputs. + +Working on the compiler, from this: + +:: + + dup dup * dup * * + +We can already generate: + +:: + + --------------------------------- + (a0, stack) = stack + a1 = mul(a0, a0) + a2 = mul(a1, a1) + a3 = mul(a2, a0) + stack = (a3, stack) + --------------------------------- + +This is pretty old stuff... (E.g. from 1999, M. Anton Ertl `Compilation +of Stack-Based +Languages `__ he +goes a lot further for Forth.) + +"A Transformation Based Approach to Semantics-Directed Code Generation" +----------------------------------------------------------------------- + +by Arthur Nunes-Harwitt + +https://dl.acm.org/citation.cfm?doid=2635648.2635652 + +.. code:: ipython2 + + def m(x, y): return x * y + + print m(2, 3) + + +.. parsed-literal:: + + 6 + + +.. code:: ipython2 + + def m(x): return lambda y: x * y + + print m(2)(3) + + +.. parsed-literal:: + + 6 + + +.. code:: ipython2 + + def m(x): return "lambda y: %(x)s * y" % locals() + + print m(2) + print eval(m(2))(3) + + +.. parsed-literal:: + + lambda y: 2 * y + 6 + + +In Joy: + +:: + + m == [*] cons + + 3 2 m i + 3 2 [*] cons i + 3 [2 *] i + 3 2 * + 6 + +.. code:: ipython2 + + def p(n, b): # original + return 1 if n == 0 else b * p(n - 1, b) + + + def p(n): # curried + return lambda b: 1 if n == 0 else b * p(n - 1, b) + + + def p(n): # quoted + return "lambda b: 1 if %(n)s == 0 else b * p(%(n)s - 1, b)" % locals() + + + print p(3) + + +.. parsed-literal:: + + lambda b: 1 if 3 == 0 else b * p(3 - 1, b) + + +Original + +:: + + p == [0 =] [popop 1] [-- over] [dip *] genrec + + b n p + b n [0 =] [popop 1] [-- over [p] dip *] + + b n -- over [p] dip * + b n-1 over [p] dip * + b n-1 b [p] dip * + b n-1 p b * + +curried, quoted + +:: + + n p + --------------------------------------------- + [[n 0 =] [pop 1] [dup n --] [*] genrec] + +.. code:: ipython2 + + def p(n): # lambda lowered + return ( + lambda b: 1 + if n == 0 else + lambda b: b * p(n - 1, b) + ) + + + def p(n): # lambda lowered quoted + return ( + "lambda b: 1" + if n == 0 else + "lambda b: b * p(%(n)s - 1, b)" % locals() + ) + + print p(3) + + +.. parsed-literal:: + + lambda b: b * p(3 - 1, b) + + +:: + + p == [0 =] [[pop 1]] [ [-- [dup] dip p *] cons ]ifte + + + 3 p + 3 [-- [dup] dip p *] cons + [3 -- [dup] dip p *] + +.. code:: ipython2 + + def p(n): # expression lifted + if n == 0: + return lambda b: 1 + f = p(n - 1) + return lambda b: b * f(b) + + + print p(3)(2) + + +.. parsed-literal:: + + 8 + + +.. code:: ipython2 + + def p(n): # quoted + if n == 0: + return "lambda b: 1" + f = p(n - 1) + return "lambda b: b * (%(f)s)(b)" % locals() + + print p(3) + print eval(p(3))(2) + + +.. parsed-literal:: + + lambda b: b * (lambda b: b * (lambda b: b * (lambda b: 1)(b))(b))(b) + 8 + + +:: + + p == [0 =] [pop [pop 1]] [-- p [dupdip *] cons] ifte + + + 3 p + 3 -- p [dupdip *] cons + 2 p [dupdip *] cons + 2 -- p [dupdip *] cons [dupdip *] cons + 1 p [dupdip *] cons [dupdip *] cons + 1 -- p [dupdip *] cons [dupdip *] cons [dupdip *] cons + 0 p [dupdip *] cons [dupdip *] cons [dupdip *] cons + 0 pop [pop 1] [dupdip *] cons [dupdip *] cons [dupdip *] cons + [pop 1] [dupdip *] cons [dupdip *] cons [dupdip *] cons + ... + [[[[pop 1] dupdip *] dupdip *] dupdip *] + + + 2 [[[[pop 1] dupdip *] dupdip *] dupdip *] i + 2 [[[pop 1] dupdip *] dupdip *] dupdip * + 2 [[pop 1] dupdip *] dupdip * 2 * + 2 [pop 1] dupdip * 2 * 2 * + 2 pop 1 2 * 2 * 2 * + 1 2 * 2 * 2 * + + + + p == [0 =] [pop [pop 1]] [-- p [dupdip *] cons] ifte + p == [0 =] [pop [pop 1]] [-- [p] i [dupdip *] cons] ifte + p == [0 =] [pop [pop 1]] [--] [i [dupdip *] cons] genrec + +.. code:: ipython2 + + define('p == [0 =] [pop [pop 1]] [--] [i [dupdip *] cons] genrec') + +.. code:: ipython2 + + J('3 p') + + +.. parsed-literal:: + + [[[[pop 1] dupdip *] dupdip *] dupdip *] + + +.. code:: ipython2 + + V('2 [[[[pop 1] dupdip *] dupdip *] dupdip *] i') + + +.. parsed-literal:: + + . 2 [[[[pop 1] dupdip *] dupdip *] dupdip *] i + 2 . [[[[pop 1] dupdip *] dupdip *] dupdip *] i + 2 [[[[pop 1] dupdip *] dupdip *] dupdip *] . i + 2 . [[[pop 1] dupdip *] dupdip *] dupdip * + 2 [[[pop 1] dupdip *] dupdip *] . dupdip * + 2 . [[pop 1] dupdip *] dupdip * 2 * + 2 [[pop 1] dupdip *] . dupdip * 2 * + 2 . [pop 1] dupdip * 2 * 2 * + 2 [pop 1] . dupdip * 2 * 2 * + 2 . pop 1 2 * 2 * 2 * + . 1 2 * 2 * 2 * + 1 . 2 * 2 * 2 * + 1 2 . * 2 * 2 * + 2 . 2 * 2 * + 2 2 . * 2 * + 4 . 2 * + 4 2 . * + 8 . + + +.. code:: ipython2 + + stack = list_to_stack([sympy.symbols('u')]) + (result, s) = run('p i', stack, D)[0] + result + +From this: + +:: + + p == [0 =] [pop pop 1] [-- over] [dip *] genrec + +To this: + +:: + + p == [0 =] [pop [pop 1]] [--] [i [dupdip *] cons] genrec + +Try it with ``F()``: +-------------------- + +.. code:: ipython2 + + def odd(n): return n % 2 + + + def F(u, k): + z = 1 + while k != 0: + if odd(k): + z = z * u + k = k / 2 + u = u * u + return z + + F(2, 5) + +.. code:: ipython2 + + def F(k): + def _F(u, k=k): + z = 1 + while k != 0: + if odd(k): + z = z * u + k = k / 2 + u = u * u + return z + return _F + + F(5)(2) + +.. code:: ipython2 + + def F(k): + def _F(u, k=k): + if k == 0: + z = 1 + else: + z = F(k / 2)(u) + z *= z + if odd(k): + z = z * u + return z + return _F + + F(5)(2) + +.. code:: ipython2 + + def F(k): + if k == 0: + z = lambda u: 1 + else: + f = F(k / 2) + def z(u): + uu = f(u) + uu *= uu + return uu * u if odd(k) else uu + return z + + F(5)(2) + +.. code:: ipython2 + + def F(k): + if k == 0: + z = lambda u: 1 + else: + f = F(k / 2) + if odd(k): + z = lambda u: (lambda fu, u: fu * fu * u)(f(u), u) + else: + z = lambda u: (lambda fu, u: fu * fu)(f(u), u) + return z + + F(5)(2) + +.. code:: ipython2 + + def F(k): + if k == 0: + z = "lambda u: 1" + else: + f = F(k / 2) + if odd(k): + z = "lambda u: (lambda fu, u: fu * fu * u)((%(f)s)(u), u)" % locals() + else: + z = "lambda u: (lambda fu, u: fu * fu)((%(f)s)(u), u)" % locals() + return z + + source = F(5) + print source + eval(source)(2) + +Hmm... + +.. code:: ipython2 + + for n in range(4): + print F(n) + +.. code:: ipython2 + + def F(k): + if k == 0: + z = "lambda u: 1" + elif k == 1: + z = "lambda u: u" + else: + f = F(k / 2) + if odd(k): + z = "lambda u: (lambda fu, u: fu * fu * u)((%(f)s)(u), u)" % locals() + else: + z = "lambda u: (lambda fu, u: fu * fu)((%(f)s)(u), u)" % locals() + return z + + source = F(5) + print source + eval(source)(2) + +.. code:: ipython2 + + for n in range(4): + print F(n) + +.. code:: ipython2 + + def F(k): + if k == 0: + z = "lambda u: 1" + elif k == 1: + z = "lambda u: u" + else: + m = k / 2 + if odd(k): + if m == 0: + z = "lambda u: 1" + elif m == 1: + z = "lambda u: u * u * u" + else: + z = "lambda u: (lambda fu, u: fu * fu * u)((%s)(u), u)" % F(m) + else: + if m == 0: + z = "lambda u: 1" + elif m == 1: + z = "lambda u: u * u" + else: + z = "lambda u: (lambda u: u * u)((%s)(u))" % F(m) + return z + + source = F(5) + print source + eval(source)(2) + +.. code:: ipython2 + + def F(k): + if k == 0: + z = "lambda u: 1" + elif k == 1: + z = "lambda u: u" + else: + m = k / 2 + if m == 0: + z = "lambda u: 1" + + elif odd(k): + if m == 1: + z = "lambda u: u * u * u" + else: + z = "lambda u: (lambda fu, u: fu * fu * u)((%s)(u), u)" % F(m) + else: + if m == 1: + z = "lambda u: u * u" + else: + z = "lambda u: (lambda u: u * u)((%s)(u))" % F(m) + return z + + source = F(5) + print source + eval(source)(2) + +.. code:: ipython2 + + for n in range(7): + source = F(n) + print n, '%2i' % eval(source)(2), source + +So that gets pretty good, eh? + +But looking back at the definition in Joy, it doesn't seem easy to +directly apply this technique to Joy code: + +:: + + Ft == [over] dip * + Fb == [[sqr] dip 2 //] dip + Fw == [pop odd] [Ft] [] ifte Fb + F == 1 [pop] [Fw] while popopd + +But a direct translation of the Python code..? + +:: + + F == [ + [[0 =] [pop 1]] + [[1 =] []] + [_F.0] + ] cond + + _F.0 == dup 2 // [ + [[0 =] [pop 1]] + [[pop odd] _F.1] + [_F.2] + ] cond + + _F.1 == [1 =] [pop [dup dup * *]] [popd F [dupdip over * *] cons] ifte + _F.2 == [1 =] [pop [dup *]] [popd F [i dup *] cons] ifte + +Try it: + +:: + + 5 F + 5 [ [[0 =] [pop 1]] [[1 =] []] [_F.0] ] cond + 5 _F.0 + 5 dup 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond + 5 5 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond + + 5 2 [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond + 5 2 _F.1 + 5 2 [1 =] [popop [dup dup * *]] [popd F [dupdip over * *] cons] ifte + 5 2 popd F [dupdip over * *] cons + 2 F [dupdip over * *] cons + + 2 F [dupdip over * *] cons + + 2 F + 2 [ [[0 =] [pop 1]] [[1 =] []] [_F.0] ] cond + 2 _F.0 + 2 dup 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond + 2 2 2 // [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond + 2 1 [ [[0 =] [pop 1]] [[pop odd] _F.1] [_F.2] ] cond + 2 1 _F.2 + 2 1 [1 =] [popop [dup *]] [popd F [i dup *] cons] ifte + 2 1 popop [dup *] + [dup *] + + + 2 F [dupdip over * *] cons + [dup *] [dupdip over * *] cons + [[dup *] dupdip over * *] + +And here it is in action: + +:: + + 2 [[dup *] dupdip over * *] i + 2 [dup *] dupdip over * * + 2 dup * 2 over * * + 2 2 * 2 over * * + 4 2 over * * + 4 2 4 * * + 4 8 * + 32 + +So, it works, but in this case the results of the partial evaluation are +more elegant. + +Try it with ``hylomorphism()``: +------------------------------- + +.. code:: ipython2 + + def hylomorphism(c, F, P, G): + '''Return a hylomorphism function H.''' + + def H(a): + if P(a): + result = c + else: + b, aa = G(a) + result = F(b, H(aa)) + return result + + return H + +First, curry +~~~~~~~~~~~~ + +With abuse of syntax: + +.. code:: ipython2 + + def hylomorphism(c): + return lambda F: lambda P: lambda G: lambda a: ( + if P(a): + result = c + else: + b, aa = G(a) + result = F(b)(H(aa)) + return result + ) + + +lambda lowering +~~~~~~~~~~~~~~~ + +.. code:: ipython2 + + def hylomorphism(c): + def r(a): + def rr(P): + if P(a): + return lambda F: lambda G: c + return lambda F: lambda G: ( + b, aa = G(a) + return F(b)(H(aa)) + ) + return rr + return r + + + +expression lifting +~~~~~~~~~~~~~~~~~~ + +.. code:: ipython2 + + def hylomorphism(c): + def r(a): + def rr(P): + def rrr(G): + if P(a): + return lambda F: c + b, aa = G(a) + H = hylomorphism(c)(aa)(P)(G) + return lambda F: F(b)(H(F)) + return rrr + return rr + return r + + + +quoted +~~~~~~ + +.. code:: ipython2 + + def hylomorphism(c): + def r(a): + def rr(P): + def rrr(G): + if P(a): + return "lambda F: %s" % (c,) + b, aa = G(a) + H = hylomorphism(c)(aa)(P)(G) + return "lambda F: F(%(b)s)((%(H)s)(F))" % locals() + return rrr + return rr + return r + + + +.. code:: ipython2 + + hylomorphism(0)(3)(lambda n: n == 0)(lambda n: (n-1, n-1)) + +.. code:: ipython2 + + def F(a): + def _F(b): + print a, b + return a + b + return _F + + +.. code:: ipython2 + + F(2)(3) + +.. code:: ipython2 + + eval(hylomorphism(0)(5)(lambda n: n == 0)(lambda n: (n-1, n-1)))(F) + +.. code:: ipython2 + + eval(hylomorphism([])(5)(lambda n: n == 0)(lambda n: (n-1, n-1)))(lambda a: lambda b: [a] + b) + +.. code:: ipython2 + + hylomorphism(0)([1, 2, 3])(lambda n: not n)(lambda n: (n[0], n[1:])) + +.. code:: ipython2 + + hylomorphism([])([1, 2, 3])(lambda n: not n)(lambda n: (n[1:], n[1:])) + +.. code:: ipython2 + + eval(hylomorphism([])([1, 2, 3])(lambda n: not n)(lambda n: (n[1:], n[1:])))(lambda a: lambda b: [a] + b) + +.. code:: ipython2 + + def hylomorphism(c): + return lambda a: lambda P: ( + if P(a): + result = lambda F: lambda G: c + else: + result = lambda F: lambda G: ( + b, aa = G(a) + return F(b)(H(aa)) + ) + return result + ) + + +.. code:: ipython2 + + def hylomorphism(c): + return lambda a: ( + lambda F: lambda P: lambda G: c + if P(a) else + lambda F: lambda P: lambda G: ( + b, aa = G(a) + return F(b)(H(aa)) + ) + ) + + +.. code:: ipython2 + + def hylomorphism(c): + return lambda a: lambda G: ( + lambda F: lambda P: c + if P(a) else + b, aa = G(a) + lambda F: lambda P: F(b)(H(aa)) + ) + diff --git a/docs/with_sympy_files/with_sympy_11_0.png b/docs/with_sympy_files/with_sympy_11_0.png new file mode 100644 index 0000000000000000000000000000000000000000..e1decf4552ca4d50f0cb59ec4548ecf72349de91 GIT binary patch literal 1201 zcmV;i1Wx;jP)9L6eN{dK6+qKOiJ_sErj~eL&p$; zf(SBtfHe3adQe)FgkUdwLYXB&mSt4-P}3}WXb+}{q^t}>N=l~>>kMA6=icr)_smt~ z{NZrt?7i38>zuv!T5In!l$4b0KlIBfHxyfnG^AwTWI8U}+eVc1JLT!$W9(}a#T?7-w&4YIE2@mSM@L0P$4 zyo4UiY+=8xWL)z5aT7X*h_U>=d2=`hj})P(LE7<$*v9S81HBG^VL%f)o=@}~P^&@O zimtWFCySup3i{zO{HC>&D-WGwB6j4EJqV-lsfe?AxEJS&lgb^n8dVSKWPF)Y4wLX^ zPGhoigGe%FU|R$2ufWsTC_KOxk(JJv+`Rskk_~ds4RtnFs5NmNh-Vz^>(s&S8;XHhir}LG#r>bnK z=CZ;@86c9q4H%r1b*Uet+i+A$`Eaqizp5&~Gtu{gn84M#Mr5?PQ-0UIAd&r5Y+WmP zGO4>gMQ*;>Lr)aX-g~uW>HKE#qAOX5BeE#T$sFN1CRCJl$)AjEz%Lc|V-x+W#d&=Z zbjm-_Yt-Iaot4x*n^M!~XFGetsD?M=GWALv6d z8y||;yrZh0wH0j_C1sNuc%C%6WqZcQ>*yLNa)WN^8TW`p$4fbF-Ho&kHj0%nMYz2Q z_^1J0-^v!lf2y)$QPTJ93_4B~Nz0K~h3CXx@*feSr(u0X`wPTmSdpQRO6J)!W{$xn zB2Vb9s;hU3I9hm>rP4F*-VpMAW-Bvrw}{ofe~;?R&cw@Wbd}7jkXtV%S4WD@IZ6BL zllwu*{kno}@08DV&rIZQs!=~TslPa{VU^U{q`!<=A}*hd&x`1+4%Ti+9& z=7XfXU99M3_WW!*)UK^n?}=0IzfH9w*Jy;^j63aodN{7d*TQ4<8U69I$WZ6@Wk(Ir zB|SzDOUgHjc-PC}V(@!}3m;Tb_gPYYc}hPc)j3(JL#KQzdQ~F#o!ztlHoPHnjSF+w zlS-`>y;H=FSw(hMFq_3bbE3#0mW!2ek%+-N#s0D~Z@3k&h*&;dc&ecy&bBA=tzdwN z(d%%qh~-PfN!e5!jT`Y=D~y>U5|?JqV2hwj?W}v4*l%tYd*=5=0yfsJ!-s0l!K-#9 zZJUI9yfUYKowBd!(-N5l;v2$Zu`k{#yipciO*2@0;4UdCDJdx_X+8e|4$c*Jo)4UM P00000NkvXXu0mjf;`UH5 literal 0 HcmV?d00001 diff --git a/docs/with_sympy_files/with_sympy_16_0.png b/docs/with_sympy_files/with_sympy_16_0.png new file mode 100644 index 0000000000000000000000000000000000000000..9c215a3f4e0b0ed92956c4881f31589a8680d87d GIT binary patch literal 2003 zcmV;^2Q2uBP)|aGAyssl1|xTZ!m~xp~GdO?gExe|*<*Py6n@&))l-yZ1TTpP4=T zoV{MZz4rS3)>^-{_J)|4n7pMJ=oYmQF)>+iG-}5M$wc7LsD;QgS_8Y|qo9uaY@cKv zP~;MJ4e%B4ZAh{d3Ksx_fPLblV2*XbO?D46UpWx?3}_U!AbH4hKuOd>#Q0_zYS(#% zQxAAM@H((FL|F=fCg%7pYC&RrQwiJ^wGdut0c-(|jaraAq#p$~l!^I|Lx62S`=|xU z#9&}^)I#JblZc&Cg-i`FJZd4*u^WO809J%8NP*Ci*eMk?ccA&)R-hJ`3moS$?F!Vs z^9DNwJPlYGwjc#Scj9`<2g-oSKrf&i*a6H3_VCD0Rh?|-DUKq820%xfeJ& zY9aoI+=$xTH38GNM9#v29^&3$%IXxbtej)D1S!F`G;q1uR(cea0?R`zZwPe@$wNad zH*8Kc=T8ErFG212R}XPh?K*Gf@m2+FWzJh0xZG?jJ!}pJeniR;1|88Zq~Aj-FG@<0 zck=Uq=_}CC@?#HiQ|;Pq9k)H|Q6~TgAmdkwHl_T9l=uVST5>RywPw)Rod3x-;|ye5 z=?#Tbg$_iM{V>=Ecna7AJRVY+K}jH^R2DFOQ?%UmDVodq$H&MHPB=E;F^z6e;+U1}q~81A(>JE8!fVSx9mW z2X>)`l_c{J^m5=?pc-uu*o?e{ZqKj`cmenxHOilWdBBA}WlfNs0-j{jPXg+^S_k(_ukNynsX=ML)4(B164>#7NE`VRwe*f0DXWpKFUkA>sp8K{ZPHk zMbCVJ`Mt{$Zv%CslQM_Af*3}MkVU-5EmD?Tk1G0gq;xL>;(1)7^{N!)wDmdF}g>3|;kmzL}IoBUovhQ*qfU+-Xo?0Uc{CjCaV%#+U3 zChShj^~q>1SB~yIb+q(Q-pwXYrw-B-iU`)h9$r= z9s~$VkV+4Wkw^j3nGUoIq3qGf>h?^#mL#|rbS*MwDY~2dm*GAK@wQV- zOe0ihPIY_^>bWv0{LR3vsC^7^h|>mb+V2k>iH5xC{+9o?dfF*q6|$%UkTP1yrm^L! z(zB5v6R)90H5MtNouRN2v{wN=^e*PN(>+Z!$9Q0bxi$+aL?2+0CH`o0PHh}$2DC*= zVRa~YrNZV~0@c+F8yPZ@S_`gm2*1dj-}0HS6KA1z;ADUu=iR z*BbcFgrAyL#%|z;9Lt^{_#E&toIe-Q0gb+@$=&x{W+Qp+XoP$cZ@8o%f#h9{yH8*h zGNffTVWUkR!`wv`mB8U%BKjcgN)JL2T3@;!dk81@W~FDtG&CG3k64Z*&Q6$jsB^T8MMKh!$ceC%`oo<5=K3x)x*3Z+ z(o4}od=L@@Q@~=ht*Z*nrPiTcEfdh@fL&;=l`CeTxtJ3ZQQg?Rta~@Q$K_;x<06P* zbfp`boaTbCD?J;|M-GdqKWCT3SpnOk`Drck-YrJ+i|*7dXj|!xlrJ?W`3cQe1_zWc zK`;e8-lUs?_SwFI*7mldhUzyzH+lo-W{C408s=NM3dy+xjfRpoN5YcEuVKtcu0G5Z zW~FCKbM)e8OyQHM{ho~4F0KgJJ*x002ovPDHLkV1jMlu{Qt! literal 0 HcmV?d00001 diff --git a/docs/with_sympy_files/with_sympy_17_0.png b/docs/with_sympy_files/with_sympy_17_0.png new file mode 100644 index 0000000000000000000000000000000000000000..199c6039148c5910be989f6c02fb7b576718c978 GIT binary patch literal 2032 zcmV6(Ce~4j8Z~OH zQKP<)h-57wDk?UxE9z>9v4dSfT(HYpve*@rhz&u&KYsVIkJ&r*&6{~o=SwEDJGY%V zbIzT6&%N^?Cnu+BsRIUPEksUE8Ek{>xF)#?*feV)s*H7kmHAOq$JN#+Sp{_PNjnAj z9QZ0FS!#umz|p`O`B5{+XTaIEhgq#`0DK5^%vz8tH%@mtn{!liK044%^0KjMBbuZSIs=$?SMVZd2xl?Em!GjvoY`;a)vbMi*_OXo>F^RQjemOAH^&` z9t|zu4w1KL*M94`z0n(W0n{F>0Fl)sP@zYd&A0z+AM1#6n~(sC;fpj@R_3NM9@ zK$HD6SOd5R_!+n@r8eW@piZeFX8Efw6$ukn!L6fQX z4q!K6CUW-2QPAg3;8FCv5~Cg|^yDNfb+Hz5@RrkFCi~#0gcfzSa=a}_IF| zGOPl@L(pS^2Y?o|L0}Pz68b&E2H+9k8)TH<1J416MU>T{Je}`s%1;D#M8^Fy@CWcb z{?X0rfggcwBeZvfX>UKDJkJ0N(JruI$S&MAu@Q=fuLO>a(0)7I6)!O79`1|@G6214 z-zB=zE5**ph_*!0wNkpG-gQX|a%=;P1#Sdh4UxY)Fa_8gcn!E2cmmiAxEp&gZ9Fgo z8P|)z4d(xP+TVMnsW<0-z#GV*?*f{UlT1gO-`z|Ajt7PUA4h1fXx9ro($_(HnTDSE zbo2KLSH2%;8=W+G)D@>=qyy>^uk_1QCZ{7sKaHI3F`y9{&`RJZ>`D*9p}gK0WCV?_ zYe$eyu&53NdFBCK%(VjkZ@_M-H+ItpJ@ijq*T8>kcdf-a;M9HRimK5UUI` zf%`)U5SKVg4}(Mb4tNoAfKu)PHY}sgu#85%?%#3R=b_H5$RJbyJNVCmqTo=T z&S&Et4d2bAJ>Z;{`rdqqVSQXbyNH@Loy&;mApC`U%5$+D{F3L{Y;j=$Vuf zv}9sw;r-}#ZG^rj=ynU{|DM{lkmhd&3n0NKY09(j7AP5TD}TcIIuX}o3C zR!^G)HlrT(5af(*$}zU2l%BLPl=76Rrx7`#t)p-g_pJhY=>5&#UV6R595({znQQkU z9Sj9tcI6*!&c%%boq=9R5AJ|MSjJnfIY?KNEiwdATnldXNI%@1pO09t7yBYx^y+{P zN6~q|x7u0g8GMYMxoz=3YR710uK+>fG77!AFYrhoXVMRfQMW_+Lr_p8a5(5&1TNn7+r0-tR#!BGZgk^V#uk%~+{<(-gX!O-Wa=+*D z5ZFb)*fJw9%%Hv;0LBO~4jmGDe`1N_R-c0Xv}er7_qe zTxn9kJ_qb&uD8&(c>=U^<2jSIjMazo#h}5_mUj9R@7E6;;_G~tDf=a>M`XJJFQ88F zfEe}K=mWfh_eP7MMwZh&(l0UTbMXJFK5ED4OK%hbOp;0}-60tV1ifvRqEL616z~qh zyhK}|r4bED7os4-a@r4b4mQ_UpwUeuib&5w3-NJqh^O;9w5_Wd&80p=yILlo%>gUW zTq`Liqq&$DHzD2FKGwYg-Q#S6zqp9gG5RtPO-_@b(n@zo&H)ca0gJ4E&aQyB1NK7m z(*-Dc_a>TO45Dp8+szQz%J;+&z#yP$bNT0c5}WZtw8W{ z%ge4ZaXQAFoc}Gw-9ejWEl5tz|B+9~f9W76CnqN-CnqN-CnqN-1^xx?b&^~iUv%C8 O0000P)NklSs+3tY;)Iz?k01iG+~J-ox?G}us*d<+%r;1Qra9G;^S z!&t@-A=_JMiv%kL0&6kep7<7UfD2)g-V066#b_jWQxHE98dww39t=jCSN|>{!!Kn7 z2BOU)8IQJFgYU#Qp_W-Zz@|{Xmt_P#Mv|Vy*MhhH{FL3blBS>CDxP5-OL&M8YzT8* x!R}u?{5P60FO1zeKH(Kwag7gU$nWv*a08koLce>Zep3Jd002ovPDHLkV1h6-r%?a^ literal 0 HcmV?d00001 diff --git a/docs/with_sympy_files/with_sympy_44_1.png b/docs/with_sympy_files/with_sympy_44_1.png new file mode 100644 index 0000000000000000000000000000000000000000..549565511420fef021e8a789b7e961b149f0957b GIT binary patch literal 403 zcmV;E0c`$>P)NklSs+3tY;)Iz?k01iG+~J-ox?G}us*d<+%r;1Qra9G;^S z!&t@-A=_JMiv%kL0&6kep7<7UfD2)g-V066#b_jWQxHE98dww39t=jCSN|>{!!Kn7 z2BOU)8IQJFgYU#Qp_W-Zz@|{Xmt_P#Mv|Vy*MhhH{FL3blBS>CDxP5-OL&M8YzT8* x!R}u?{5P60FO1zeKH(Kwag7gU$nWv*a08koLce>Zep3Jd002ovPDHLkV1h6-r%?a^ literal 0 HcmV?d00001 diff --git a/docs/with_sympy_files/with_sympy_9_0.png b/docs/with_sympy_files/with_sympy_9_0.png new file mode 100644 index 0000000000000000000000000000000000000000..f9b2b3031413aaa5875c708f5cdd5e53783450ef GIT binary patch literal 956 zcmV;t14I0YP);GHBF*OQ<)$#nqC-DRyKWD`!c8Fxp(HC zx#Ni3KWq+X?Y-9ipS$*Y>`NPMv;pqLX6(Z#*5kYxWz>ctEEA6C!8&}8lV+0Ck%rqb zhFeSOci;f7hND_Usvd3@WXG_4n)jMXEK1K$OX|-PzWBK{bn0UPe#LK&e5Y)~ADCTI z|D|N(=$0`mz$^@7Hmol+6}`b+8`i@)$mN&VH>i?eHj=}KQKFd&NA?~_=61D?lD z5m~>8?qpd5oqln`zQum=VMDmKEa{Ty!5opgdhrT=65sun#$DAzSc)asRTEB!Q_jT$ zcogqVV*NZkgR}5H9>=RV6Hnpp0-Zr@5RMo8tGh|g6;n%JYm)wt z=*!JLo96Dz$)6B+lmYSWwb436Z+p}?5q`qdSw&(=Zj)^DmLLo(X0P0 zYRXb<&#b>(IBr7|dt@=_;QZc;YeZce$<2K>fxR9PvFpWw%%he{ziEnPi_mHEr*AN$ zw_O~9{sNn;()v5n``q-tt;O6c%BQd8N%yWg^N*zYtC}%d_Og@5e$jChVUef>ABY;U zG07K*NEyPrIh{`9?QrduJS#m28R`@ifhXJB;P4=a}6g8 zM~#UzHa9c(d6HjWu+KTl-H0p7N6Mz8yT|`K!4AAF>f;s78f{gil)vN_c0D4U9xv+8YvP7~9Ztucc=Hf)nl!5F zcVF{GFFz{oBp-?5-e>$0md7P{!+1lmSEPp@y(b3 literal 0 HcmV?d00001 diff --git a/joy/utils/infinite_stack.py b/joy/utils/infinite_stack.py new file mode 100644 index 0000000..10367c8 --- /dev/null +++ b/joy/utils/infinite_stack.py @@ -0,0 +1,30 @@ +from joy.parser import Symbol + + +def _names(): + n = 0 + while True: + yield Symbol('a' + str(n)) + n += 1 + + +class InfiniteStack(tuple): + + names = _names().next + + def __iter__(self): + if not self: + return iter((self.names(), self)) + + +i = InfiniteStack() + +a, b = i + +lambda u: (lambda fu, u: fu * fu * u)( + (lambda u: (lambda fu, u: fu * fu)( + (lambda u: (lambda fu, u: fu * fu * u)( + (lambda u: 1)(u), u))(u), u))(u), + u) + +lambda u: (lambda fu, u: fu * fu * u)((lambda u: (lambda fu, u: fu * fu)((lambda u: (lambda fu, u: fu * fu * u)((lambda u: 1)(u), u))(u), u))(u), u)