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.
This commit is contained in:
Simon Forman 2018-08-24 16:48:15 -07:00
parent 3e6a49ac27
commit 3b0b7659b3
20 changed files with 48204 additions and 0 deletions

13543
docs/Compiling_Joy.html Normal file

File diff suppressed because it is too large Load Diff

1310
docs/Compiling_Joy.ipynb Normal file

File diff suppressed because it is too large Load Diff

724
docs/Compiling_Joy.md Normal file
View File

@ -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
```
<ipython-input-74-a6ea700b09d9>: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

788
docs/Compiling_Joy.rst Normal file
View File

@ -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::
<ipython-input-74-a6ea700b09d9>: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

12319
docs/fun_with_scan.html Normal file

File diff suppressed because it is too large Load Diff

441
docs/fun_with_scan.ipynb Normal file
View File

@ -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
}

234
docs/fun_with_scan.md Normal file
View File

@ -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.

334
docs/fun_with_scan.rst Normal file
View File

@ -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.

BIN
docs/notebook_preamble.pyc Normal file

Binary file not shown.

14034
docs/with_sympy.html Normal file

File diff suppressed because it is too large Load Diff

1873
docs/with_sympy.ipynb Normal file

File diff suppressed because it is too large Load Diff

1261
docs/with_sympy.md Normal file

File diff suppressed because it is too large Load Diff

1313
docs/with_sympy.rst Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

View File

@ -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)