Thun/docs/with_sympy.rst

1314 lines
34 KiB
ReStructuredText

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 <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:
.. 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 <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:
.. 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 <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:
.. 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 <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
.. 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))
)