diff --git a/docs/sphinx_docs/_build/html/notebooks/Generator_Programs.html b/docs/sphinx_docs/_build/html/notebooks/Generator_Programs.html new file mode 100644 index 0000000..cb7830d --- /dev/null +++ b/docs/sphinx_docs/_build/html/notebooks/Generator_Programs.html @@ -0,0 +1,569 @@ + + + + + + + + Using x to Generate Values — Thun 0.2.0 documentation + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Using x to Generate Values

+

Cf. jp-reprod.html

+
from notebook_preamble import J, V, define
+
+
+

Consider the x combinator:

+
x == dup i
+
+
+

We can apply it to a quoted program consisting of some value a and +some function B:

+
[a B] x
+[a B] a B
+
+
+

Let B function swap the a with the quote and run some +function C on it to generate a new value b:

+
B == swap [C] dip
+
+[a B] a B
+[a B] a swap [C] dip
+a [a B]      [C] dip
+a C [a B]
+b [a B]
+
+
+

Now discard the quoted a with rest then cons b:

+
b [a B] rest cons
+b [B]        cons
+[b B]
+
+
+

Altogether, this is the definition of B:

+
B == swap [C] dip rest cons
+
+
+

We can make a generator for the Natural numbers (0, 1, 2, …) by using +0 for a and [dup ++] for [C]:

+
[0 swap [dup ++] dip rest cons]
+
+
+

Let’s try it:

+
V('[0 swap [dup ++] dip rest cons] x')
+
+
+
                                           . [0 swap [dup ++] dip rest cons] x
+           [0 swap [dup ++] dip rest cons] . x
+           [0 swap [dup ++] dip rest cons] . 0 swap [dup ++] dip rest cons
+         [0 swap [dup ++] dip rest cons] 0 . swap [dup ++] dip rest cons
+         0 [0 swap [dup ++] dip rest cons] . [dup ++] dip rest cons
+0 [0 swap [dup ++] dip rest cons] [dup ++] . dip rest cons
+                                         0 . dup ++ [0 swap [dup ++] dip rest cons] rest cons
+                                       0 0 . ++ [0 swap [dup ++] dip rest cons] rest cons
+                                       0 1 . [0 swap [dup ++] dip rest cons] rest cons
+       0 1 [0 swap [dup ++] dip rest cons] . rest cons
+         0 1 [swap [dup ++] dip rest cons] . cons
+         0 [1 swap [dup ++] dip rest cons] .
+
+
+

After one application of x the quoted program contains 1 and +0 is below it on the stack.

+
J('[0 swap [dup ++] dip rest cons] x x x x x pop')
+
+
+
0 1 2 3 4
+
+
+
+

direco

+
define('direco == dip rest cons')
+
+
+
V('[0 swap [dup ++] direco] x')
+
+
+
                                    . [0 swap [dup ++] direco] x
+           [0 swap [dup ++] direco] . x
+           [0 swap [dup ++] direco] . 0 swap [dup ++] direco
+         [0 swap [dup ++] direco] 0 . swap [dup ++] direco
+         0 [0 swap [dup ++] direco] . [dup ++] direco
+0 [0 swap [dup ++] direco] [dup ++] . direco
+0 [0 swap [dup ++] direco] [dup ++] . dip rest cons
+                                  0 . dup ++ [0 swap [dup ++] direco] rest cons
+                                0 0 . ++ [0 swap [dup ++] direco] rest cons
+                                0 1 . [0 swap [dup ++] direco] rest cons
+       0 1 [0 swap [dup ++] direco] . rest cons
+         0 1 [swap [dup ++] direco] . cons
+         0 [1 swap [dup ++] direco] .
+
+
+
+
+

Making Generators

+

We want to define a function that accepts a and [C] and builds +our quoted program:

+
         a [C] G
+-------------------------
+   [a swap [C] direco]
+
+
+

Working in reverse:

+
[a swap   [C] direco] cons
+a [swap   [C] direco] concat
+a [swap] [[C] direco] swap
+a [[C] direco] [swap]
+a [C] [direco] cons [swap]
+
+
+

Reading from the bottom up:

+
G == [direco] cons [swap] swap concat cons
+G == [direco] cons [swap] swoncat cons
+
+
+
define('G == [direco] cons [swap] swoncat cons')
+
+
+

Let’s try it out:

+
J('0 [dup ++] G')
+
+
+
[0 swap [dup ++] direco]
+
+
+
J('0 [dup ++] G x x x pop')
+
+
+
0 1 2
+
+
+
+

Powers of 2

+
J('1 [dup 1 <<] G x x x x x x x x x pop')
+
+
+
1 2 4 8 16 32 64 128 256
+
+
+
+
+

[x] times

+

If we have one of these quoted programs we can drive it using times +with the x combinator.

+
J('23 [dup ++] G 5 [x] times')
+
+
+
23 24 25 26 27 [28 swap [dup ++] direco]
+
+
+
+
+
+

Generating Multiples of Three and Five

+

Look at the treatment of the Project Euler Problem One in the +“Developing a Program” notebook and you’ll see that we might be +interested in generating an endless cycle of:

+
3 2 1 3 1 2 3
+
+
+

To do this we want to encode the numbers as pairs of bits in a single +int:

+
    3  2  1  3  1  2  3
+0b 11 10 01 11 01 10 11 == 14811
+
+
+

And pick them off by masking with 3 (binary 11) and then shifting the +int right two bits.

+
define('PE1.1 == dup [3 &] dip 2 >>')
+
+
+
V('14811 PE1.1')
+
+
+
                  . 14811 PE1.1
+            14811 . PE1.1
+            14811 . dup [3 &] dip 2 >>
+      14811 14811 . [3 &] dip 2 >>
+14811 14811 [3 &] . dip 2 >>
+            14811 . 3 & 14811 2 >>
+          14811 3 . & 14811 2 >>
+                3 . 14811 2 >>
+          3 14811 . 2 >>
+        3 14811 2 . >>
+           3 3702 .
+
+
+

If we plug 14811 and [PE1.1] into our generator form…

+
J('14811 [PE1.1] G')
+
+
+
[14811 swap [PE1.1] direco]
+
+
+

…we get a generator that works for seven cycles before it reaches +zero:

+
J('[14811 swap [PE1.1] direco] 7 [x] times')
+
+
+
3 2 1 3 1 2 3 [0 swap [PE1.1] direco]
+
+
+
+

Reset at Zero

+

We need a function that checks if the int has reached zero and resets it +if so.

+
define('PE1.1.check == dup [pop 14811] [] branch')
+
+
+
J('14811 [PE1.1.check PE1.1] G')
+
+
+
[14811 swap [PE1.1.check PE1.1] direco]
+
+
+
J('[14811 swap [PE1.1.check PE1.1] direco] 21 [x] times')
+
+
+
3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 [0 swap [PE1.1.check PE1.1] direco]
+
+
+

(It would be more efficient to reset the int every seven cycles but +that’s a little beyond the scope of this article. This solution does +extra work, but not much, and we’re not using it “in production” as they +say.)

+
+
+

Run 466 times

+

In the PE1 problem we are asked to sum all the multiples of three and +five less than 1000. It’s worked out that we need to use all seven +numbers sixty-six times and then four more.

+
J('7 66 * 4 +')
+
+
+
466
+
+
+

If we drive our generator 466 times and sum the stack we get 999.

+
J('[14811 swap [PE1.1.check PE1.1] direco] 466 [x] times')
+
+
+
3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 [57 swap [PE1.1.check PE1.1] direco]
+
+
+
J('[14811 swap [PE1.1.check PE1.1] direco] 466 [x] times pop enstacken sum')
+
+
+
999
+
+
+
+
+
+

Project Euler Problem One

+
define('PE1.2 == + dup [+] dip')
+
+
+

Now we can add PE1.2 to the quoted program given to G.

+
J('0 0 0 [PE1.1.check PE1.1] G 466 [x [PE1.2] dip] times popop')
+
+
+
233168
+
+
+
+
+

A generator for the Fibonacci Sequence.

+

Consider:

+
[b a F] x
+[b a F] b a F
+
+
+

The obvious first thing to do is just add b and a:

+
[b a F] b a +
+[b a F] b+a
+
+
+

From here we want to arrive at:

+
b [b+a b F]
+
+
+

Let’s start with swons:

+
[b a F] b+a swons
+[b+a b a F]
+
+
+

Considering this quote as a stack:

+
F a b b+a
+
+
+

We want to get it to:

+
F b b+a b
+
+
+

So:

+
F a b b+a popdd over
+F b b+a b
+
+
+

And therefore:

+
[b+a b a F] [popdd over] infra
+[b b+a b F]
+
+
+

But we can just use cons to carry b+a into the quote:

+
[b a F] b+a [popdd over] cons infra
+[b a F] [b+a popdd over]      infra
+[b b+a b F]
+
+
+

Lastly:

+
[b b+a b F] uncons
+b [b+a b F]
+
+
+

Putting it all together:

+
F == + [popdd over] cons infra uncons
+fib_gen == [1 1 F]
+
+
+
define('fib == + [popdd over] cons infra uncons')
+
+
+
define('fib_gen == [1 1 fib]')
+
+
+
J('fib_gen 10 [x] times')
+
+
+
1 2 3 5 8 13 21 34 55 89 [144 89 fib]
+
+
+
+
+

Project Euler Problem Two

+
By considering the terms in the Fibonacci sequence whose values do not exceed four million,
+find the sum of the even-valued terms.
+
+
+

Now that we have a generator for the Fibonacci sequence, we need a +function that adds a term in the sequence to a sum if it is even, and +pops it otherwise.

+
define('PE2.1 == dup 2 % [+] [pop] branch')
+
+
+

And a predicate function that detects when the terms in the series +“exceed four million”.

+
define('>4M == 4000000 >')
+
+
+

Now it’s straightforward to define PE2 as a recursive function that +generates terms in the Fibonacci sequence until they exceed four million +and sums the even ones.

+
define('PE2 == 0 fib_gen x [pop >4M] [popop] [[PE2.1] dip x] primrec')
+
+
+
J('PE2')
+
+
+
4613732
+
+
+

Here’s the collected program definitions:

+
fib == + swons [popdd over] infra uncons
+fib_gen == [1 1 fib]
+
+even == dup 2 %
+>4M == 4000000 >
+
+PE2.1 == even [+] [pop] branch
+PE2 == 0 fib_gen x [pop >4M] [popop] [[PE2.1] dip x] primrec
+
+
+
+

Even-valued Fibonacci Terms

+

Using o for odd and e for even:

+
o + o = e
+e + e = e
+o + e = o
+
+
+

So the Fibonacci sequence considered in terms of just parity would be:

+
o o e o o e o o e o o e o o e o o e
+1 1 2 3 5 8 . . .
+
+
+

Every third term is even.

+
J('[1 0 fib] x x x')  # To start the sequence with 1 1 2 3 instead of 1 2 3.
+
+
+
1 1 2 [3 2 fib]
+
+
+

Drive the generator three times and popop the two odd terms.

+
J('[1 0 fib] x x x [popop] dipd')
+
+
+
2 [3 2 fib]
+
+
+
define('PE2.2 == x x x [popop] dipd')
+
+
+
J('[1 0 fib] 10 [PE2.2] times')
+
+
+
2 8 34 144 610 2584 10946 46368 196418 832040 [1346269 832040 fib]
+
+
+

Replace x with our new driver function PE2.2 and start our +fib generator at 1 0.

+
J('0 [1 0 fib] PE2.2 [pop >4M] [popop] [[PE2.1] dip PE2.2] primrec')
+
+
+
4613732
+
+
+
+
+
+

How to compile these?

+

You would probably start with a special version of G, and perhaps +modifications to the default x?

+
+
+

An Interesting Variation

+
define('codireco == cons dip rest cons')
+
+
+
V('[0 [dup ++] codireco] x')
+
+
+
                                 . [0 [dup ++] codireco] x
+           [0 [dup ++] codireco] . x
+           [0 [dup ++] codireco] . 0 [dup ++] codireco
+         [0 [dup ++] codireco] 0 . [dup ++] codireco
+[0 [dup ++] codireco] 0 [dup ++] . codireco
+[0 [dup ++] codireco] 0 [dup ++] . cons dip rest cons
+[0 [dup ++] codireco] [0 dup ++] . dip rest cons
+                                 . 0 dup ++ [0 [dup ++] codireco] rest cons
+                               0 . dup ++ [0 [dup ++] codireco] rest cons
+                             0 0 . ++ [0 [dup ++] codireco] rest cons
+                             0 1 . [0 [dup ++] codireco] rest cons
+       0 1 [0 [dup ++] codireco] . rest cons
+         0 1 [[dup ++] codireco] . cons
+         0 [1 [dup ++] codireco] .
+
+
+
define('G == [codireco] cons cons')
+
+
+
J('230 [dup ++] G 5 [x] times pop')
+
+
+
230 231 232 233 234
+
+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/docs/sphinx_docs/_build/html/notebooks/Ordered_Binary_Trees.html b/docs/sphinx_docs/_build/html/notebooks/Ordered_Binary_Trees.html new file mode 100644 index 0000000..054a8b4 --- /dev/null +++ b/docs/sphinx_docs/_build/html/notebooks/Ordered_Binary_Trees.html @@ -0,0 +1,1415 @@ + + + + + + + + Treating Trees I: Ordered Binary Trees — Thun 0.2.0 documentation + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Treating Trees I: Ordered Binary Trees

+

Although any expression in Joy can be considered to describe a +tree with the quotes +as compound nodes and the non-quote values as leaf nodes, in this page I +want to talk about ordered binary +trees and how to +make and use them.

+

The basic structure, in a crude type +notation, is:

+
Tree :: [] | [key value Tree Tree]
+
+
+

That says that a Tree is either the empty quote [] or a quote with +four items: a key, a value, and two Trees representing the left and +right branches of the tree.

+

We’re going to derive some recursive functions to work with such +datastructures:

+
Tree-add
+Tree-delete
+Tree-get
+Tree-iter
+Tree-iter-order
+
+
+

Once these functions are defined we have a new “type” to work with, and +the Sufficiently Smart Compiler can be modified to use an optimized +implementation under the hood. (Where does the “type” come from? It has +a contingent existence predicated on the disciplined use of these +functions on otherwise undistinguished Joy datastructures.)

+
from notebook_preamble import D, J, V, define, DefinitionWrapper
+
+
+
+

Adding Nodes to the Tree

+

Let’s consider adding nodes to a Tree structure.

+
   Tree value key Tree-add
+-----------------------------
+            Tree′
+
+
+
+

Adding to an empty node.

+

If the current node is [] then you just return +[key value [] []]:

+
Tree-add == [popop not] [[pop] dipd Tree-new] [R0] [R1] genrec
+
+
+
+

Tree-new

+

Where Tree-new is defined as:

+
   value key Tree-new
+------------------------
+   [key value [] []]
+
+
+

Example:

+
value key swap [[] []] cons cons
+key value      [[] []] cons cons
+key      [value [] []]      cons
+     [key value [] []]
+
+
+

Definition:

+
Tree-new == swap [[] []] cons cons
+
+
+
define('Tree-new == swap [[] []] cons cons')
+
+
+
J('"v" "k" Tree-new')
+
+
+
['k' 'v' [] []]
+
+
+

(As an implementation detail, the [[] []] literal used in the +definition of Tree-new will be reused to supply the constant tail +for all new nodes produced by it. This is one of those cases where you +get amortized storage “for free” by using persistent +datastructures. +Because the tail, which is ((), ((), ())) in Python, is immutable +and embedded in the definition body for Tree-new, all new nodes can +reuse it as their own tail without fear that some other code somewhere +will change it.)

+
+
+
+

Adding to a non-empty node.

+

We now have to derive R0 and R1, consider:

+
[key_n value_n left right] value key R0 [Tree-add] R1
+
+
+

In this case, there are three possibilites: the key can be greater or +less than or equal to the node’s key. In two of those cases we will need +to apply a copy of Tree-add, so R0 is pretty much out of the +picture.

+
[R0] == []
+
+
+
+

A predicate to compare keys.

+
[key_n value_n left right] value key [BTree-add] R1
+
+
+

The first thing we need to do is compare the the key we’re adding to the +node key and branch accordingly:

+
[key_n value_n left right] value key [BTree-add] [P] [T] [E] ifte
+
+
+

That would suggest something like:

+
[key_n value_n left right] value key [BTree-add] P
+[key_n value_n left right] value key [BTree-add] pop roll> pop first >
+[key_n value_n left right] value key                 roll> pop first >
+key [key_n value_n left right] value                 roll> pop first >
+key key_n                                                            >
+Boolean
+
+
+

Let’s abstract the predicate just a little to let us specify the +comparison operator:

+
P > == pop roll> pop first >
+P < == pop roll> pop first <
+P   == pop roll> pop first
+
+
+
define('P == pop roll> pop first')
+
+
+
J('["old_key" 23 [] []] 17 "new_key" ["..."] P')
+
+
+
'new_key' 'old_key'
+
+
+
+
+

If the key we’re adding is greater than the node’s key.

+

Here the parentheses are meant to signify that the expression is not +literal, the code in the parentheses is meant to have been evaluated:

+
   [key_n value_n left right] value key [Tree-add] T
+-------------------------------------------------------
+   [key_n value_n left (Tree-add key value right)]
+
+
+

So how do we do this? We’re going to want to use infra on some +function K that has the key and value to work with, as well as the +quoted copy of Tree-add to apply somehow. Considering the node as a +stack:

+
   right left value_n key_n value key [Tree-add] K
+-----------------------------------------------------
+   right value key Tree-add left value_n key_n
+
+
+

Pretty easy:

+
right left value_n key_n value key [Tree-add] cons cons dipdd
+right left value_n key_n [value key Tree-add]           dipdd
+right value key Tree-add left value_n key_n
+
+
+

So:

+
K == cons cons dipdd
+
+
+

Looking at it from the point-of-view of the node as node again:

+
[key_n value_n left right] [value key [Tree-add] K] infra
+
+
+

Expand K and evaluate a little:

+
[key_n value_n left right] [value key [Tree-add] K] infra
+[key_n value_n left right] [value key [Tree-add] cons cons dipdd] infra
+[key_n value_n left right] [[value key Tree-add]           dipdd] infra
+
+
+

Then, working backwards:

+
[key_n value_n left right] [[value key Tree-add]           dipdd]      infra
+[key_n value_n left right] [value key Tree-add]           [dipdd] cons infra
+[key_n value_n left right] value key [Tree-add] cons cons [dipdd] cons infra
+
+
+

And so T is just:

+
T == cons cons [dipdd] cons infra
+
+
+
define('T == cons cons [dipdd] cons infra')
+
+
+
J('["old_k" "old_value" "left" "right"] "new_value" "new_key" ["Tree-add"] T')
+
+
+
['old_k' 'old_value' 'left' 'Tree-add' 'new_key' 'new_value' 'right']
+
+
+
+
+

If the key we’re adding is less than the node’s key.

+

This is very very similar to the above:

+
[key_n value_n left right] value key [Tree-add] E
+[key_n value_n left right] value key [Tree-add] [P <] [Te] [Ee] ifte
+
+
+
define('E == [P <] [Te] [Ee] ifte')
+
+
+

In this case Te works that same as T but on the left child tree +instead of the right, so the only difference is that it must use +dipd instead of dipdd:

+
Te == cons cons [dipd] cons infra
+
+
+
define('Te == cons cons [dipd] cons infra')
+
+
+
J('["old_k" "old_value" "left" "right"] "new_value" "new_key" ["Tree-add"] Te')
+
+
+
['old_k' 'old_value' 'Tree-add' 'new_key' 'new_value' 'left' 'right']
+
+
+
+
+

Else the keys must be equal.

+

This means we must find:

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

This is another easy one:

+
Ee == pop swap roll< rest rest cons cons
+
+
+

Example:

+
[key old_value left right] new_value key [Tree-add] pop swap roll< rest rest cons cons
+[key old_value left right] new_value key                swap roll< rest rest cons cons
+[key old_value left right] key new_value                     roll< rest rest cons cons
+key new_value [key old_value left right]                           rest rest cons cons
+key new_value [              left right]                                     cons cons
+              [key new_value left right]
+
+
+
define('Ee == pop swap roll< rest rest cons cons')
+
+
+
J('["k" "old_value" "left" "right"] "new_value" "k" ["Tree-add"] Ee')
+
+
+
['k' 'new_value' 'left' 'right']
+
+
+
+
+
+

Now we can define Tree-add

+
Tree-add == [popop not] [[pop] dipd Tree-new] [] [[P >] [T] [E] ifte] genrec
+
+
+

Putting it all together:

+
Tree-new == swap [[] []] cons cons
+P == pop roll> pop first
+T == cons cons [dipdd] cons infra
+Te == cons cons [dipd] cons infra
+Ee == pop swap roll< rest rest cons cons
+E == [P <] [Te] [Ee] ifte
+R == [P >] [T] [E] ifte
+
+Tree-add == [popop not] [[pop] dipd Tree-new] [] [R] genrec
+
+
+
define('Tree-add == [popop not] [[pop] dipd Tree-new] [] [[P >] [T] [E] ifte] genrec')
+
+
+
+
+

Examples

+
J('[] 23 "b" Tree-add')  # Initial
+
+
+
['b' 23 [] []]
+
+
+
J('["b" 23 [] []] 88 "c" Tree-add')  # Greater than
+
+
+
['b' 23 [] ['c' 88 [] []]]
+
+
+
J('["b" 23 [] []] 88 "a" Tree-add')  # Less than
+
+
+
['b' 23 ['a' 88 [] []] []]
+
+
+
J('["b" 23 [] []] 88 "b" Tree-add')  # Equal to
+
+
+
['b' 88 [] []]
+
+
+
J('[] 23 "b" Tree-add 88 "a" Tree-add 44 "c" Tree-add')  # Series.
+
+
+
['b' 23 ['a' 88 [] []] ['c' 44 [] []]]
+
+
+
J('[] [[23 "b"] [88 "a"] [44 "c"]] [i Tree-add] step')
+
+
+
['b' 23 ['a' 88 [] []] ['c' 44 [] []]]
+
+
+
+
+
+

Interlude: cmp combinator

+

Instead of mucking about with nested ifte combinators let’s use +cmp which takes two values and three quoted programs on the stack +and runs one of the three depending on the results of comparing the two +values:

+
   a b [G] [E] [L] cmp
+------------------------- a > b
+        G
+
+   a b [G] [E] [L] cmp
+------------------------- a = b
+            E
+
+   a b [G] [E] [L] cmp
+------------------------- a < b
+                L
+
+
+
J("1 0 ['G'] ['E'] ['L'] cmp")
+
+
+
'G'
+
+
+
J("1 1 ['G'] ['E'] ['L'] cmp")
+
+
+
'E'
+
+
+
J("0 1 ['G'] ['E'] ['L'] cmp")
+
+
+
'L'
+
+
+
+

Redefine Tree-add

+

We need a new non-destructive predicate P:

+
   [node_key node_value left right] value key [Tree-add] P
+------------------------------------------------------------------------
+   [node_key node_value left right] value key [Tree-add] key node_key
+
+
+

Let’s start with over to get a copy of the key and then apply some +function Q with the nullary combinator so it can dig out the +node key (by throwing everything else away):

+
P == over [Q] nullary
+
+[node_key node_value left right] value key [Tree-add] over [Q] nullary
+[node_key node_value left right] value key [Tree-add] key  [Q] nullary
+
+
+

And Q would be:

+
Q == popop popop first
+
+[node_key node_value left right] value key [Tree-add] key Q
+[node_key node_value left right] value key [Tree-add] key popop popop first
+[node_key node_value left right] value key                      popop first
+[node_key node_value left right]                                      first
+ node_key
+
+
+

Or just:

+
P == over [popop popop first] nullary
+
+
+
define('P == over [popop popop first] nullary')
+
+
+

Using cmp to simplify our code above at +``R1` <#Adding-to-a-non-empty-node.>`__:

+
[node_key node_value left right] value key [Tree-add] R1
+[node_key node_value left right] value key [Tree-add] P [T] [E] [Te] cmp
+
+
+

The line above becomes one of the three lines below:

+
[node_key node_value left right] value key [Tree-add] T
+[node_key node_value left right] value key [Tree-add] E
+[node_key node_value left right] value key [Tree-add] Te
+
+
+

The definition is a little longer but, I think, more elegant and easier +to understand:

+
Tree-add == [popop not] [[pop] dipd Tree-new] [] [P [T] [Ee] [Te] cmp] genrec
+
+
+
define('Tree-add == [popop not] [[pop] dipd Tree-new] [] [P [T] [Ee] [Te] cmp] genrec')
+
+
+
J('[] 23 "b" Tree-add 88 "a" Tree-add 44 "c" Tree-add')  # Still works.
+
+
+
['b' 23 ['a' 88 [] []] ['c' 44 [] []]]
+
+
+
+
+
+

A Function to Traverse this Structure

+

Let’s take a crack at writing a function that can recursively iterate or +traverse these trees.

+
+

Base case []

+

The stopping predicate just has to detect the empty list:

+
Tree-iter == [not] [E] [R0] [R1] genrec
+
+
+

And since there’s nothing at this node, we just pop it:

+
Tree-iter == [not] [pop] [R0] [R1] genrec
+
+
+
+
+

Node case [key value left right]

+

Now we need to figure out R0 and R1:

+
Tree-iter == [not] [pop] [R0]           [R1] genrec
+          == [not] [pop] [R0 [Tree-iter] R1] ifte
+
+
+

Let’s look at it in situ:

+
[key value left right] R0 [Tree-iter] R1
+
+
+
+

Processing the current node.

+

R0 is almost certainly going to use dup to make a copy of the +node and then dip on some function to process the copy with it:

+
[key value left right] [F] dupdip                 [Tree-iter] R1
+[key value left right]  F  [key value left right] [Tree-iter] R1
+
+
+

For example, if we’re getting all the keys F would be first:

+
R0 == [first] dupdip
+
+[key value left right] [first] dupdip                 [Tree-iter] R1
+[key value left right]  first  [key value left right] [Tree-iter] R1
+key                            [key value left right] [Tree-iter] R1
+
+
+
+
+

Recur

+

Now R1 needs to apply [Tree-iter] to left and right. If +we drop the key and value from the node using rest twice we are left +with an interesting situation:

+
key [key value left right] [Tree-iter] R1
+key [key value left right] [Tree-iter] [rest rest] dip
+key [key value left right] rest rest [Tree-iter]
+key [left right] [Tree-iter]
+
+
+

Hmm, will step do?

+
key [left right] [Tree-iter] step
+key left Tree-iter [right] [Tree-iter] step
+key left-keys [right] [Tree-iter] step
+key left-keys right Tree-iter
+key left-keys right-keys
+
+
+

Neat. So:

+
R1 == [rest rest] dip step
+
+
+
+
+
+

Putting it together

+

We have:

+
Tree-iter == [not] [pop] [[F] dupdip] [[rest rest] dip step] genrec
+
+
+

When I was reading this over I realized rest rest could go in +R0:

+
Tree-iter == [not] [pop] [[F] dupdip rest rest] [step] genrec
+
+
+

(And [step] genrec is such a cool and suggestive combinator!)

+
+
+

Parameterizing the F per-node processing function.

+
                [F] Tree-iter
+------------------------------------------------------
+   [not] [pop] [[F] dupdip rest rest] [step] genrec
+
+
+

Working backward:

+
[not] [pop] [[F] dupdip rest rest]            [step] genrec
+[not] [pop] [F]       [dupdip rest rest] cons [step] genrec
+[F] [not] [pop] roll< [dupdip rest rest] cons [step] genrec
+
+
+
+
+

Tree-iter

+
Tree-iter == [not] [pop] roll< [dupdip rest rest] cons [step] genrec
+
+
+
define('Tree-iter == [not] [pop] roll< [dupdip rest rest] cons [step] genrec')
+
+
+
+
+

Examples

+
J('[] [foo] Tree-iter')  #  It doesn't matter what F is as it won't be used.
+
+
+
J("['b' 23 ['a' 88 [] []] ['c' 44 [] []]] [first] Tree-iter")
+
+
+
'b' 'a' 'c'
+
+
+
J("['b' 23 ['a' 88 [] []] ['c' 44 [] []]] [second] Tree-iter")
+
+
+
23 88 44
+
+
+
+
+
+

Interlude: A Set-like Datastructure

+

We can use this to make a set-like datastructure by just setting values +to e.g. 0 and ignoring them. It’s set-like in that duplicate items added +to it will only occur once within it, and we can query it in +:math:`O(log_2 N) <https://en.wikipedia.org/wiki/Binary_search_tree#cite_note-2>`__ +time.

+
J('[] [3 9 5 2 8 6 7 8 4] [0 swap Tree-add] step')
+
+
+
[3 0 [2 0 [] []] [9 0 [5 0 [4 0 [] []] [8 0 [6 0 [] [7 0 [] []]] []]] []]]
+
+
+
define('to_set == [] swap [0 swap Tree-add] step')
+
+
+
J('[3 9 5 2 8 6 7 8 4] to_set')
+
+
+
[3 0 [2 0 [] []] [9 0 [5 0 [4 0 [] []] [8 0 [6 0 [] [7 0 [] []]] []]] []]]
+
+
+

And with that we can write a little program unique to remove +duplicate items from a list.

+
define('unique == [to_set [first] Tree-iter] cons run')
+
+
+
J('[3 9 3 5 2 9 8 8 8 6 2 7 8 4 3] unique')  # Filter duplicate items.
+
+
+
[7 6 8 4 5 9 2 3]
+
+
+
+
+

A Version of Tree-iter that does In-Order Traversal

+

If you look back to the non-empty case of the ``Tree-iter` +function <#Node-case-%5Bkey-value-left-right%5D>`__ we can design a +variant that first processes the left child, then the current node, then +the right child. This will allow us to traverse the tree in sort order.

+
Tree-iter-order == [not] [pop] [R0] [R1] genrec
+
+
+

To define R0 and R1 it helps to look at them as they will appear +when they run:

+
[key value left right] R0 [BTree-iter-order] R1
+
+
+
+

Process the left child.

+

Staring at this for a bit suggests dup third to start:

+
[key value left right] R0        [Tree-iter-order] R1
+[key value left right] dup third [Tree-iter-order] R1
+[key value left right] left      [Tree-iter-order] R1
+
+
+

Now maybe:

+
[key value left right] left [Tree-iter-order] [cons dip] dupdip
+[key value left right] left [Tree-iter-order]  cons dip [Tree-iter-order]
+[key value left right] [left Tree-iter-order]       dip [Tree-iter-order]
+left Tree-iter-order [key value left right]             [Tree-iter-order]
+
+
+
+
+

Process the current node.

+

So far, so good. Now we need to process the current node’s values:

+
left Tree-iter-order [key value left right] [Tree-iter-order] [[F] dupdip] dip
+left Tree-iter-order [key value left right] [F] dupdip [Tree-iter-order]
+left Tree-iter-order [key value left right] F [key value left right] [Tree-iter-order]
+
+
+

If F needs items from the stack below the left stuff it should have +cons’d them before beginning maybe? For functions like first it +works fine as-is.

+
left Tree-iter-order [key value left right] first [key value left right] [Tree-iter-order]
+left Tree-iter-order key [key value left right] [Tree-iter-order]
+
+
+
+
+

Process the right child.

+

First ditch the rest of the node and get the right child:

+
left Tree-iter-order key [key value left right] [Tree-iter-order] [rest rest rest first] dip
+left Tree-iter-order key right [Tree-iter-order]
+
+
+

Then, of course, we just need i to run Tree-iter-order on the +right side:

+
left Tree-iter-order key right [Tree-iter-order] i
+left Tree-iter-order key right Tree-iter-order
+
+
+
+
+

Defining Tree-iter-order

+

The result is a little awkward:

+
R1 == [cons dip] dupdip [[F] dupdip] dip [rest rest rest first] dip i
+
+
+

Let’s do a little semantic factoring:

+
fourth == rest rest rest first
+
+proc_left == [cons dip] dupdip
+proc_current == [[F] dupdip] dip
+proc_right == [fourth] dip i
+
+Tree-iter-order == [not] [pop] [dup third] [proc_left proc_current proc_right] genrec
+
+
+

Now we can sort sequences.

+
#define('Tree-iter-order == [not] [pop] [dup third] [[cons dip] dupdip [[first] dupdip] dip [rest rest rest first] dip i] genrec')
+
+
+DefinitionWrapper.add_definitions('''
+
+fourth == rest rest rest first
+
+proc_left == [cons dip] dupdip
+proc_current == [[first] dupdip] dip
+proc_right == [fourth] dip i
+
+Tree-iter-order == [not] [pop] [dup third] [proc_left proc_current proc_right] genrec
+
+''', D)
+
+
+
J('[3 9 5 2 8 6 7 8 4] to_set Tree-iter-order')
+
+
+
2 3 4 5 6 7 8 9
+
+
+

Parameterizing the [F] function is left as an exercise for the +reader.

+
+
+
+

Getting values by key

+

Let’s derive a function that accepts a tree and a key and returns the +value associated with that key.

+
   tree key Tree-get
+-----------------------
+        value
+
+
+

But what do we do if the key isn’t in the tree? In Python we might raise +a KeyError but I’d like to avoid exceptions in Joy if possible, and +here I think it’s possible. (Division by zero is an example of where I +think it’s probably better to let Python crash Joy. Sometimes the +machinery fails and you have to “stop the line”, I think.)

+

Let’s pass the buck to the caller by making the base case a given, you +have to decide for yourself what [E] should be.

+
   tree key [E] Tree-get
+---------------------------- key in tree
+           value
+
+   tree key [E] Tree-get
+---------------------------- key not in tree
+         [] key E
+
+
+
+

The base case []

+

As before, the stopping predicate just has to detect the empty list:

+
Tree-get == [pop not] [E] [R0] [R1] genrec
+
+
+

So we define:

+
Tree-get == [pop not] swap [R0] [R1] genrec
+
+
+

Note that this Tree-get creates a slightly different function than +itself and that function does the actual recursion. This kind of +higher-level programming is unusual in most languages but natural in +Joy.

+
tree key [E] [pop not] swap [R0] [R1] genrec
+tree key [pop not] [E] [R0] [R1] genrec
+
+
+

The anonymous specialized recursive function that will do the real work.

+
[pop not] [E] [R0] [R1] genrec
+
+
+
+
+

Node case [key value left right]

+

Now we need to figure out R0 and R1:

+
[key value left right] key R0 [BTree-get] R1
+
+
+

We want to compare the search key with the key in the node, and if they +are the same return the value, otherwise recur on one of the child +nodes. So it’s very similar to the above funtion, with [R0] == [] +and R1 == P [T>] [E] [T<] cmp:

+
[key value left right] key [BTree-get] P [T>] [E] [T<] cmp
+
+
+
+

Predicate

+
P == over [get-node-key] nullary
+get-node-key == pop popop first
+
+
+

The only difference is that get-node-key does one less pop +because there’s no value to discard.

+
+
+

Branches

+

Now we have to derive the branches:

+
[key_n value_n left right] key [BTree-get] T>
+[key_n value_n left right] key [BTree-get] E
+[key_n value_n left right] key [BTree-get] T<
+
+
+
+
+

Greater than and less than

+

The cases of T> and T< are similar to above but instead of using +infra we have to discard the rest of the structure:

+
   [key_n value_n left right] key [BTree-get] T>
+---------------------------------------------------
+                       right  key  BTree-get
+
+
+

And:

+
   [key_n value_n left right] key [BTree-get] T<
+---------------------------------------------------
+                  left        key  BTree-get
+
+
+

So:

+
T> == [fourth] dipd i
+T< == [third] dipd i
+
+
+

E.g.:

+
[key_n value_n left right]        key [BTree-get] [fourth] dipd i
+[key_n value_n left right] fourth key [BTree-get]               i
+                    right         key [BTree-get]               i
+                    right         key  BTree-get
+
+
+
+
+

Equal keys

+

Return the node’s value:

+
[key_n value_n left right] key [BTree-get] E == value_n
+
+E == popop second
+
+
+
+
+
+

Tree-get

+

So:

+
fourth == rest rest rest first
+get-node-key == pop popop first
+P == over [get-node-key] nullary
+T> == [fourth] dipd i
+T< == [third] dipd i
+E == popop second
+
+Tree-get == [pop not] swap [] [P [T>] [E] [T<] cmp] genrec
+
+
+
# I don't want to deal with name conflicts with the above so I'm inlining everything here.
+# The original Joy system has "hide" which is a meta-command which allows you to use named
+# definitions that are only in scope for a given definition.  I don't want to implement
+# that (yet) so...
+
+
+define('''
+Tree-get == [pop not] swap [] [
+  over [pop popop first] nullary
+  [[fourth] dipd i]
+  [popop second]
+  [[third] dipd i]
+  cmp
+  ] genrec
+''')
+
+
+
J('["gary" 23 [] []] "mike" [popd " not in tree" +] Tree-get')
+
+
+
'mike not in tree'
+
+
+
J('["gary" 23 [] []] "gary" [popop "err"] Tree-get')
+
+
+
23
+
+
+
J('''
+
+    [] [[0 'a'] [1 'b'] [2 'c']] [i Tree-add] step
+
+    'c' [popop 'not found'] Tree-get
+
+''')
+
+
+
2
+
+
+
J('''
+
+    [] [[0 'a'] [1 'b'] [2 'c']] [i Tree-add] step
+
+    'd' [popop 'not found'] Tree-get
+
+''')
+
+
+
'not found'
+
+
+
+
+
+

Tree-delete

+

Now let’s write a function that can return a tree datastructure with a +key, value pair deleted:

+
   tree key Tree-delete
+---------------------------
+          tree
+
+
+

If the key is not in tree it just returns the tree unchanged.

+
+

Base case

+

Same as above.

+
Tree-Delete == [pop not] [pop] [R0] [R1] genrec
+
+
+
+
+

Recur

+

Now we get to figure out the recursive case. We need the node’s key to +compare and we need to carry the key into recursive branches. Let D +be shorthand for Tree-Delete:

+
D == Tree-Delete == [pop not] [pop] [R0] [R1] genrec
+
+[node_key node_value left right] key R0                   [D] R1
+[node_key node_value left right] key over  first swap dup [D] cons R1′
+[node_key node_value left right] key [...] first swap dup [D] cons R1′
+[node_key node_value left right] key node_key    swap dup [D] cons R1′
+[node_key node_value left right] node_key key         dup [D] cons R1′
+[node_key node_value left right] node_key key key         [D] cons R1′
+[node_key node_value left right] node_key key         [key D]      R1′
+
+
+

And then:

+
[node_key node_value left right] node_key key [key D] R1′
+[node_key node_value left right] node_key key [key D] roll> [T>] [E] [T<] cmp
+[node_key node_value left right] node_key key [key D] roll> [T>] [E] [T<] cmp
+[node_key node_value left right] [key D] node_key key       [T>] [E] [T<] cmp
+
+
+

So:

+
R0 == over first swap dup
+R1 == cons roll> [T>] [E] [T<] cmp
+
+
+
+
+

Compare Keys

+

The last line above:

+
[node_key node_value left right] [key D] node_key key [T>] [E] [T<] cmp
+
+
+

Then becomes one of these three:

+
[node_key node_value left right] [key D] T>
+[node_key node_value left right] [key D] E
+[node_key node_value left right] [key D] T<
+
+
+
+
+

Greater than case and less than case

+
   [node_key node_value left right] [F] T>
+-------------------------------------------------
+   [node_key node_value (left F) right]
+
+
+   [node_key node_value left right] [F] T<
+-------------------------------------------------
+   [node_key node_value left (right F)]
+
+
+

First, treating the node as a stack:

+
right left       node_value node_key [key D] dipd
+right left key D node_value node_key
+right left'      node_value node_key
+
+
+

Ergo:

+
[node_key node_value left right] [key D] [dipd] cons infra
+
+
+

So:

+
T> == [dipd] cons infra
+T< == [dipdd] cons infra
+
+
+
+
+

The else case

+

We have found the node in the tree where key equals node_key. We +need to replace the current node with something

+
   [node_key node_value left right] [key D] E
+------------------------------------------------
+                    tree
+
+
+

We have to handle three cases, so let’s use cond.

+
+

One or more child nodes are []

+

The first two cases are symmetrical: if we only have one non-empty child +node return it. If both child nodes are empty return an empty node.

+
E == [
+    [[pop third not] pop fourth]
+    [[pop fourth not] pop third]
+    [default]
+] cond
+
+
+
+
+

Both child nodes are non-empty.

+

If both child nodes are non-empty, we find the highest node in our lower +sub-tree, take its key and value to replace (delete) our own, then get +rid of it by recursively calling delete() on our lower sub-node with our +new key.

+

(We could also find the lowest node in our higher sub-tree and take its +key and value and delete it. I only implemented one of these two +symmetrical options. Over a lot of deletions this might make the tree +more unbalanced. Oh well.)

+

The initial structure of the default function:

+
default == [E′] cons infra
+
+[node_key node_value left right] [key D] default
+[node_key node_value left right] [key D] [E′] cons infra
+[node_key node_value left right] [[key D] E′]      infra
+
+right left node_value node_key [key D] E′
+
+
+

First things first, we no longer need this node’s key and value:

+
right left node_value node_key [key D] roll> popop E″
+right left [key D] node_value node_key       popop E″
+right left [key D]                                 E″
+
+
+
+
+

We have to we find the highest (right-most) node in our lower (left) sub-tree:

+
right left [key D] E″
+
+
+

Ditch the key:

+
right left [key D] rest E‴
+right left     [D]      E‴
+
+
+

Find the right-most node:

+
right left        [D] [dup W] dip E⁗
+right left dup  W [D]             E⁗
+right left left W [D]             E⁗
+
+
+

Consider:

+
left W
+
+
+

We know left is not empty:

+
[L_key L_value L_left L_right] W
+
+
+

We want to keep extracting the right node as long as it is not empty:

+
W.rightmost == [P] [B] while
+
+left W.rightmost W′
+
+
+

The predicate:

+
[L_key L_value L_left L_right] P
+[L_key L_value L_left L_right] fourth
+                      L_right
+
+
+

This can run on [] so must be guarded:

+
?fourth ==  [] [fourth] [] ifte
+
+
+

( if_not_empty == [] swap [] ifte ?fourth == [fourth] if_not_empty )

+

The body is just fourth:

+
left [?fourth] [fourth] while W′
+rightest                      W′
+
+
+

So:

+
W.rightmost == [?fourth] [fourth] while
+
+
+
+
+

Found right-most node in our left sub-tree

+

We know rightest is not empty:

+
[R_key R_value R_left R_right] W′
+[R_key R_value R_left R_right] W′
+[R_key R_value R_left R_right] uncons uncons pop
+R_key [R_value R_left R_right]        uncons pop
+R_key R_value [R_left R_right]               pop
+R_key R_value
+
+
+

So:

+
W == [?fourth] [fourth] while uncons uncons pop
+
+
+

And:

+
right left left W        [D] E⁗
+right left R_key R_value [D] E⁗
+
+
+
+
+

Replace current node key and value, recursively delete rightmost

+

Final stretch. We want to end up with something like:

+
right left [R_key D] i R_value R_key
+right left  R_key D    R_value R_key
+right left′            R_value R_key
+
+
+

If we adjust our definition of W to include over at the end:

+
W == [fourth] [fourth] while uncons uncons pop over
+
+
+

That will give us:

+
right left R_key R_value R_key [D] E⁗
+
+right left         R_key R_value R_key [D] cons dipd E⁗′
+right left         R_key R_value [R_key D]      dipd E⁗′
+right left R_key D R_key R_value                     E⁗′
+right left′        R_key R_value                     E⁗′
+right left′        R_key R_value                     swap
+right left′ R_value R_key
+
+
+

So:

+
E′ == roll> popop E″
+
+E″ == rest E‴
+
+E‴ == [dup W] dip E⁗
+
+E⁗ == cons dipdd swap
+
+
+

Substituting:

+
W == [fourth] [fourth] while uncons uncons pop over
+E′ == roll> popop rest [dup W] dip cons dipd swap
+E == [
+    [[pop third not] pop fourth]
+    [[pop fourth not] pop third]
+    [[E′] cons infra]
+] cond
+
+
+

Minor rearrangement, move dup into W:

+
W == dup [fourth] [fourth] while uncons uncons pop over
+E′ == roll> popop rest [W] dip cons dipd swap
+E == [
+    [[pop third not] pop fourth]
+    [[pop fourth not] pop third]
+    [[E′] cons infra]
+] cond
+
+
+
+
+
+

Refactoring

+
W.rightmost == [fourth] [fourth] while
+W.unpack == uncons uncons pop
+W == dup W.rightmost W.unpack over
+E.clear_stuff == roll> popop rest
+E.delete == cons dipd
+E.0 == E.clear_stuff [W] dip E.delete swap
+E == [
+    [[pop third not] pop fourth]
+    [[pop fourth not] pop third]
+    [[E.0] cons infra]
+] cond
+T> == [dipd] cons infra
+T< == [dipdd] cons infra
+R0 == over first swap dup
+R1 == cons roll> [T>] [E] [T<] cmp
+BTree-Delete == [pop not] swap [R0] [R1] genrec
+
+
+

By the standards of the code I’ve written so far, this is a huge Joy +program.

+
DefinitionWrapper.add_definitions('''
+first_two == uncons uncons pop
+fourth == rest rest rest first
+?fourth == [] [fourth] [] ifte
+W.rightmost == [?fourth] [fourth] while
+E.clear_stuff == roll> popop rest
+E.delete == cons dipd
+W == dup W.rightmost first_two over
+E.0 == E.clear_stuff [W] dip E.delete swap
+E == [[[pop third not] pop fourth] [[pop fourth not] pop third] [[E.0] cons infra]] cond
+T> == [dipd] cons infra
+T< == [dipdd] cons infra
+R0 == over first swap dup
+R1 == cons roll> [T>] [E] [T<] cmp
+Tree-Delete == [pop not] [pop] [R0] [R1] genrec
+''', D)
+
+
+
J("['a' 23 [] ['b' 88 [] ['c' 44 [] []]]] 'c' Tree-Delete ")
+
+
+
['a' 23 [] ['b' 88 [] []]]
+
+
+
J("['a' 23 [] ['b' 88 [] ['c' 44 [] []]]] 'b' Tree-Delete ")
+
+
+
['a' 23 [] ['c' 44 [] []]]
+
+
+
J("['a' 23 [] ['b' 88 [] ['c' 44 [] []]]] 'a' Tree-Delete ")
+
+
+
['b' 88 [] ['c' 44 [] []]]
+
+
+
J("['a' 23 [] ['b' 88 [] ['c' 44 [] []]]] 'der' Tree-Delete ")
+
+
+
['a' 23 [] ['b' 88 [] ['c' 44 [] []]]]
+
+
+
J('[] [4 2 3 1 6 7 5 ] [0 swap Tree-add] step')
+
+
+
[4 0 [2 0 [1 0 [] []] [3 0 [] []]] [6 0 [5 0 [] []] [7 0 [] []]]]
+
+
+
J("[4 0 [2 0 [1 0 [] []] [3 0 [] []]] [6 0 [5 0 [] []] [7 0 [] []]]] 3 Tree-Delete ")
+
+
+
[4 0 [2 0 [1 0 [] []] []] [6 0 [5 0 [] []] [7 0 [] []]]]
+
+
+
J("[4 0 [2 0 [1 0 [] []] [3 0 [] []]] [6 0 [5 0 [] []] [7 0 [] []]]] 4 Tree-Delete ")
+
+
+
[3 0 [2 0 [1 0 [] []] []] [6 0 [5 0 [] []] [7 0 [] []]]]
+
+
+
+
+
+

Appendix: The source code.

+
fourth == rest_two rest first
+?fourth == [] [fourth] [] ifte
+first_two == uncons uncons pop
+ccons == cons cons
+cinf == cons infra
+rest_two == rest rest
+
+_Tree_T> == [dipd] cinf
+_Tree_T< == [dipdd] cinf
+
+_Tree_add_P == over [popop popop first] nullary
+_Tree_add_T> == ccons _Tree_T<
+_Tree_add_T< == ccons _Tree_T>
+_Tree_add_Ee == pop swap roll< rest_two ccons
+_Tree_add_R == _Tree_add_P [_Tree_add_T>] [_Tree_add_Ee] [_Tree_add_T<] cmp
+_Tree_add_E == [pop] dipd Tree-new
+
+_Tree_iter_order_left == [cons dip] dupdip
+_Tree_iter_order_current == [[F] dupdip] dip
+_Tree_iter_order_right == [fourth] dip i
+_Tree_iter_order_R == _Tree_iter_order_left _Tree_iter_order_current _Tree_iter_order_right
+
+_Tree_get_P == over [pop popop first] nullary
+_Tree_get_T> == [fourth] dipd i
+_Tree_get_T< == [third] dipd i
+_Tree_get_E == popop second
+_Tree_get_R == _Tree_get_P [_Tree_get_T>] [_Tree_get_E] [_Tree_get_T<] cmp
+
+_Tree_delete_rightmost == [?fourth] [fourth] while
+_Tree_delete_clear_stuff == roll> popop rest
+_Tree_delete_del == dip cons dipd swap
+_Tree_delete_W == dup _Tree_delete_rightmost first_two over
+_Tree_delete_E.0 == _Tree_delete_clear_stuff [_Tree_delete_W] _Tree_delete_del
+_Tree_delete_E == [[[pop third not] pop fourth] [[pop fourth not] pop third] [[_Tree_delete_E.0] cinf]] cond
+_Tree_delete_R0 == over first swap dup
+_Tree_delete_R1 == cons roll> [_Tree_T>] [_Tree_delete_E] [_Tree_T<] cmp
+
+Tree-new == swap [[] []] ccons
+Tree-add == [popop not] [_Tree_add_E] [] [_Tree_add_R] genrec
+Tree-iter == [not] [pop] roll< [dupdip rest_two] cons [step] genrec
+Tree-iter-order == [not] [pop] [dup third] [_Tree_iter_order_R] genrec
+Tree-get == [pop not] swap [] [_Tree_get_R] genrec
+Tree-delete == [pop not] [pop] [_Tree_delete_R0] [_Tree_delete_R1] genrec
+
+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/docs/sphinx_docs/_build/html/notebooks/Recursion_Combinators.html b/docs/sphinx_docs/_build/html/notebooks/Recursion_Combinators.html new file mode 100644 index 0000000..b00986e --- /dev/null +++ b/docs/sphinx_docs/_build/html/notebooks/Recursion_Combinators.html @@ -0,0 +1,686 @@ + + + + + + + + Recursive Combinators — Thun 0.2.0 documentation + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
from notebook_preamble import D, DefinitionWrapper, J, V, define
+
+
+
+

Recursive Combinators

+

This article describes the genrec combinator, how to use it, and +several generic specializations.

+
                      [if] [then] [rec1] [rec2] genrec
+---------------------------------------------------------------------
+   [if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte
+
+
+

From “Recursion Theory and Joy” (j05cmp.html) by Manfred von Thun:

+
+
“The genrec combinator takes four program parameters in addition to +whatever data parameters it needs. Fourth from the top is an +if-part, followed by a then-part. If the if-part yields true, then +the then-part is executed and the combinator terminates. The other +two parameters are the rec1-part and the rec2-part. If the if-part +yields false, the rec1-part is executed. Following that the four +program parameters and the combinator are again pushed onto the +stack bundled up in a quoted form. Then the rec2-part is executed, +where it will find the bundled form. Typically it will then execute +the bundled form, either with i or with app2, or some other +combinator.”
+
+

Designing Recursive Functions

+

The way to design one of these is to fix your base case and test and +then treat R1 and R2 as an else-part “sandwiching” a quotation +of the whole function.

+

For example, given a (general recursive) function F:

+
F == [I] [T] [R1]   [R2] genrec
+  == [I] [T] [R1 [F] R2] ifte
+
+
+

If the [I] predicate is false you must derive R1 and R2 +from:

+
... R1 [F] R2
+
+
+

Set the stack arguments in front and figure out what R1 and R2 +have to do to apply the quoted [F] in the proper way.

+
+
+

Primitive Recursive Functions

+

Primitive recursive functions are those where R2 == i.

+
P == [I] [T] [R] primrec
+  == [I] [T] [R [P] i] ifte
+  == [I] [T] [R P] ifte
+
+
+
+
+

Hylomorphism

+

A +hylomorphism +is a recursive function H :: A -> C that converts a value of type +A into a value of type C by means of:

+
    +
  • A generator G :: A -> (B, A)
  • +
  • A combiner F :: (B, C) -> C
  • +
  • A predicate P :: A -> Bool to detect the base case
  • +
  • A base case value c :: C
  • +
  • Recursive calls (zero or more); it has a “call stack in the form of a +cons list”.
  • +
+

It may be helpful to see this function implemented in imperative Python +code.

+
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))  # b is stored in the stack frame during recursive call to H().
+        return result
+
+    return H
+
+
+

Cf. “Bananas, Lenses, & Barbed +Wire”

+

Note that during evaluation of H() the intermediate b values are +stored in the Python call stack. This is what is meant by “call stack in +the form of a cons list”.

+
+
+

Hylomorphism in Joy

+

We can define a combinator hylomorphism that will make a +hylomorphism combinator H from constituent parts.

+
H == [P] c [G] [F] hylomorphism
+
+
+

The function H is recursive, so we start with ifte and set the +else-part to some function J that will contain a quoted copy of +H. (The then-part just discards the leftover a and replaces it +with the base case value c.)

+
H == [P] [pop c] [J] ifte
+
+
+

The else-part J gets just the argument a on the stack.

+
a J
+a G              The first thing to do is use the generator G
+aa b             which produces b and a new aa
+aa b [H] dip     we recur with H on the new aa
+aa H b F         and run F on the result.
+
+
+

This gives us a definition for J.

+
J == G [H] dip F
+
+
+

Plug it in and convert to genrec.

+
H == [P] [pop c] [G [H] dip F] ifte
+H == [P] [pop c] [G]   [dip F] genrec
+
+
+

This is the form of a hylomorphism in Joy, which nicely illustrates that +it is a simple specialization of the general recursion combinator.

+
H == [P] c [G] [F] hylomorphism == [P] [pop c] [G] [dip F] genrec
+
+
+
+
+

Derivation of hylomorphism combinator

+

Now we just need to derive a definition that builds the genrec +arguments out of the pieces given to the hylomorphism combinator.

+
   [P]      c  [G]     [F] hylomorphism
+------------------------------------------
+   [P] [pop c] [G] [dip F] genrec
+
+
+

Working in reverse:

+
    +
  • Use swoncat twice to decouple [c] and [F].
  • +
  • Use unit to dequote c.
  • +
  • Use dipd to untangle [unit [pop] swoncat] from the givens.
  • +
+

So:

+
H == [P] [pop c]              [G]                  [dip F] genrec
+     [P] [c]    [pop] swoncat [G]        [F] [dip] swoncat genrec
+     [P] c unit [pop] swoncat [G]        [F] [dip] swoncat genrec
+     [P] c [G] [F] [unit [pop] swoncat] dipd [dip] swoncat genrec
+
+
+

At this point all of the arguments (givens) to the hylomorphism are to +the left so we have a definition for hylomorphism:

+
hylomorphism == [unit [pop] swoncat] dipd [dip] swoncat genrec
+
+
+
define('hylomorphism == [unit [pop] swoncat] dipd [dip] swoncat genrec')
+
+
+
+

Example: Finding Triangular Numbers

+

Let’s write a function that, given a positive integer, returns the sum +of all positive integers less than that one. (In this case the types +A, B and C are all int.)

+

To sum a range of integers from 0 to n - 1:

+
    +
  • [P] is [1 <=]
  • +
  • c is 0
  • +
  • [G] is [-- dup]
  • +
  • [F] is [+]
  • +
+
define('triangular_number == [1 <=] 0 [-- dup] [+] hylomorphism')
+
+
+

Let’s try it:

+
J('5 triangular_number')
+
+
+
10
+
+
+
J('[0 1 2 3 4 5 6] [triangular_number] map')
+
+
+
[0 0 1 3 6 10 15]
+
+
+
+
+
+

Four Specializations

+

There are at least four kinds of recursive combinator, depending on two +choices. The first choice is whether the combiner function F should +be evaluated during the recursion or pushed into the pending expression +to be “collapsed” at the end. The second choice is whether the combiner +needs to operate on the current value of the datastructure or the +generator’s output, in other words, whether F or G should run +first in the recursive branch.

+
H1 ==        [P] [pop c] [G             ] [dip F] genrec
+H2 == c swap [P] [pop]   [G [F]    dip  ] [i]     genrec
+H3 ==        [P] [pop c] [  [G] dupdip  ] [dip F] genrec
+H4 == c swap [P] [pop]   [  [F] dupdip G] [i]     genrec
+
+
+

The working of the generator function G differs slightly for each. +Consider the recursive branches:

+
... a G [H1] dip F                w/ a G == a′ b
+
+... c a G [F] dip H2                 a G == b  a′
+
+... a [G] dupdip [H3] dip F          a G == a′
+
+... c a [F] dupdip G H4              a G == a′
+
+
+

The following four sections illustrate how these work, omitting the +predicate evaluation.

+
+

H1

+
H1 == [P] [pop c] [G] [dip F] genrec
+
+
+

Iterate n times.

+
... a  G [H1] dip F
+... a′ b [H1] dip F
+... a′ H1 b F
+... a′ G [H1] dip F b F
+... a″ b′ [H1] dip F b F
+... a″ H1 b′ F b F
+... a″ G [H1] dip F b′ F b F
+... a‴ b″ [H1] dip F b′ F b F
+... a‴ H1 b″ F b′ F b F
+... a‴ pop c b″ F b′ F b F
+... c b″ F b′ F b F
+... d      b′ F b F
+... d′          b F
+... d″
+
+
+

This form builds up a pending expression (continuation) that contains +the intermediate results along with the pending combiner functions. When +the base case is reached the last term is replaced by the identity value +c and the continuation “collapses” into the final result using the +combiner F.

+
+
+

H2

+

When you can start with the identity value c on the stack and the +combiner F can operate as you go using the intermediate results +immediately rather than queuing them up, use this form. An important +difference is that the generator function must return its results in the +reverse order.

+
H2 == c swap [P] [pop] [G [F] dip] primrec
+
+... c a G  [F] dip H2
+... c b a′ [F] dip H2
+... c b F a′ H2
+... d     a′ H2
+... d a′ G  [F] dip H2
+... d b′ a″ [F] dip H2
+... d b′ F a″ H2
+... d′     a″ H2
+... d′ a″ G  [F] dip H2
+... d′ b″ a‴ [F] dip H2
+... d′ b″ F a‴ H2
+... d″      a‴ H2
+... d″ a‴ pop
+... d″
+
+
+
+
+

H3

+

If you examine the traces above you’ll see that the combiner F only +gets to operate on the results of G, it never “sees” the first value +a. 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″
+
+
+
+
+

H4

+

And, last but not least, if you can combine as you go, starting with +c, and the combiner F needs to work on the current item, this is +the form:

+
H4 == c swap [P] [pop] [[F] dupdip G] primrec
+
+... c  a  [F] dupdip G H4
+... c  a   F  a      G H4
+... d         a      G H4
+... d  a′              H4
+... d  a′ [F] dupdip G H4
+... d  a′  F  a′     G H4
+... d′        a′     G H4
+... d′ a″              H4
+... d′ a″ [F] dupdip G H4
+... d′ a″  F  a″     G H4
+... d″        a″     G H4
+... d″ a‴              H4
+... d″ a‴ pop
+... d″
+
+
+
+
+
+

Anamorphism

+

An anamorphism can be defined as a hylomorphism that uses [] for +c and swons for F. An anamorphic function builds a list of +values.

+
A == [P] [] [G] [swons] hylomorphism
+
+
+
+

range et. al.

+

An example of an anamorphism is the range function which generates +the list of integers from 0 to n - 1 given n.

+

Each of the above variations can be used to make four slightly different +range functions.

+
+

range with H1

+
H1 == [P]    [pop c]  [G]      [dip F]     genrec
+   == [0 <=] [pop []] [-- dup] [dip swons] genrec
+
+
+
define('range == [0 <=] [] [-- dup] [swons] hylomorphism')
+
+
+
J('5 range')
+
+
+
[4 3 2 1 0]
+
+
+
+
+

range with H2

+
H2 == c  swap [P]    [pop] [G      [F]     dip] primrec
+   == [] swap [0 <=] [pop] [-- dup [swons] dip] primrec
+
+
+
define('range_reverse == [] swap [0 <=] [pop] [-- dup [swons] dip] primrec')
+
+
+
J('5 range_reverse')
+
+
+
[0 1 2 3 4]
+
+
+
+
+

range with H3

+
H3 == [P]    [pop c]  [[G]  dupdip] [dip F]     genrec
+   == [0 <=] [pop []] [[--] dupdip] [dip swons] genrec
+
+
+
define('ranger == [0 <=] [pop []] [[--] dupdip] [dip swons] genrec')
+
+
+
J('5 ranger')
+
+
+
[5 4 3 2 1]
+
+
+
+
+

range with H4

+
H4 == c  swap [P]    [pop] [[F]     dupdip G ] primrec
+   == [] swap [0 <=] [pop] [[swons] dupdip --] primrec
+
+
+
define('ranger_reverse == [] swap [0 <=] [pop] [[swons] dupdip --] primrec')
+
+
+
J('5 ranger_reverse')
+
+
+
[1 2 3 4 5]
+
+
+

Hopefully this illustrates the workings of the variations. For more +insight you can run the cells using the V() function instead of the +J() function to get a trace of the Joy evaluation.

+
+
+
+
+

Catamorphism

+

A catamorphism can be defined as a hylomorphism that uses +[uncons swap] for [G] and [[] =] (or just [not]) for the +predicate [P]. A catamorphic function tears down a list term-by-term +and makes some new value.

+
C == [not] c [uncons swap] [F] hylomorphism
+
+
+
define('swuncons == uncons swap')  # Awkward name.
+
+
+

An example of a catamorphism is the sum function.

+
sum == [not] 0 [swuncons] [+] hylomorphism
+
+
+
define('sum == [not] 0 [swuncons] [+] hylomorphism')
+
+
+
J('[5 4 3 2 1] sum')
+
+
+
15
+
+
+
+

The step combinator

+

The step combinator will usually be better to use than +catamorphism.

+
J('[step] help')
+
+
+
Run a quoted program on each item in a sequence.
+::
+
+        ... [] [Q] . step
+     -----------------------
+               ... .
+
+
+       ... [a] [Q] . step
+    ------------------------
+             ... a . Q
+
+
+     ... [a b c] [Q] . step
+  ----------------------------------------
+               ... a . Q [b c] [Q] step
+
+The step combinator executes the quotation on each member of the list
+on top of the stack.
+
+
+
define('sum == 0 swap [+] step')
+
+
+
J('[5 4 3 2 1] sum')
+
+
+
15
+
+
+
+
+
+

Example: Factorial Function

+

For the Factorial function:

+
H4 == c swap [P] [pop] [[F] dupdip G] primrec
+
+
+

With:

+
c == 1
+F == *
+G == --
+P == 1 <=
+
+
+
define('factorial == 1 swap [1 <=] [pop] [[*] dupdip --] primrec')
+
+
+
J('5 factorial')
+
+
+
120
+
+
+
+
+

Example: tails

+

An example of a paramorphism for lists given in the “Bananas…” +paper +is tails which returns the list of “tails” of a list.

+
    [1 2 3] tails
+--------------------
+   [[] [3] [2 3]]
+
+
+

We can build as we go, and we want F to run after G, so we use +pattern H2:

+
H2 == c swap [P] [pop] [G [F] dip] primrec
+
+
+

We would use:

+
c == []
+F == swons
+G == rest dup
+P == not
+
+
+
define('tails == [] swap [not] [pop] [rest dup [swons] dip] primrec')
+
+
+
J('[1 2 3] tails')
+
+
+
[[] [3] [2 3]]
+
+
+
+
+

Conclusion: Patterns of Recursion

+

Our story so far…

+
+

Hylo-, Ana-, Cata-

+
H == [P  ] [pop c ] [G          ] [dip F        ] genrec
+A == [P  ] [pop []] [G          ] [dip swap cons] genrec
+C == [not] [pop c ] [uncons swap] [dip F        ] genrec
+
+
+
+
+

Para-, ?-, ?-

+
P == c  swap [P  ] [pop] [[F        ] dupdip G          ] primrec
+? == [] swap [P  ] [pop] [[swap cons] dupdip G          ] primrec
+? == c  swap [not] [pop] [[F        ] dupdip uncons swap] primrec
+
+
+
+
+
+

Appendix: Fun with Symbols

+
|[ (c, F), (G, P) ]| == (|c, F|) • [(G, P)]
+
+
+

“Bananas, Lenses, & Barbed +Wire”

+
(|...|)  [(...)]  [<...>]
+
+
+

I think they are having slightly too much fun with the symbols. However, +“Too much is always better than not enough.”

+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/docs/sphinx_docs/_build/html/notebooks/Replacing.html b/docs/sphinx_docs/_build/html/notebooks/Replacing.html new file mode 100644 index 0000000..d1345dd --- /dev/null +++ b/docs/sphinx_docs/_build/html/notebooks/Replacing.html @@ -0,0 +1,226 @@ + + + + + + + + Replacing Functions in the Dictionary — Thun 0.2.0 documentation + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Replacing Functions in the Dictionary

+

For now, there is no way to define new functions from within the Joy +language. All functions (and the interpreter) all accept and return a +dictionary parameter (in addition to the stack and expression) so that +we can implement e.g. a function that adds new functions to the +dictionary. However, there’s no function that does that. Adding a new +function to the dictionary is a meta-interpreter action, you have to do +it in Python, not Joy.

+
from notebook_preamble import D, J, V
+
+
+
+

A long trace

+
V('[23 18] average')
+
+
+
                                  . [23 18] average
+                          [23 18] . average
+                          [23 18] . [sum 1.0 *] [size] cleave /
+              [23 18] [sum 1.0 *] . [size] cleave /
+       [23 18] [sum 1.0 *] [size] . cleave /
+       [23 18] [sum 1.0 *] [size] . [i] app2 [popd] dip /
+   [23 18] [sum 1.0 *] [size] [i] . app2 [popd] dip /
+[23 18] [[sum 1.0 *] [23 18]] [i] . infra first [[size] [23 18]] [i] infra first [popd] dip /
+              [23 18] [sum 1.0 *] . i [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
+                          [23 18] . sum 1.0 * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
+                               41 . 1.0 * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
+                           41 1.0 . * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
+                             41.0 . [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
+                   41.0 [[23 18]] . swaack first [[size] [23 18]] [i] infra first [popd] dip /
+                   [23 18] [41.0] . first [[size] [23 18]] [i] infra first [popd] dip /
+                     [23 18] 41.0 . [[size] [23 18]] [i] infra first [popd] dip /
+    [23 18] 41.0 [[size] [23 18]] . [i] infra first [popd] dip /
+[23 18] 41.0 [[size] [23 18]] [i] . infra first [popd] dip /
+                   [23 18] [size] . i [41.0 [23 18]] swaack first [popd] dip /
+                          [23 18] . size [41.0 [23 18]] swaack first [popd] dip /
+                          [23 18] . 0 swap [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
+                        [23 18] 0 . swap [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
+                        0 [23 18] . [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
+               0 [23 18] [pop ++] . step [41.0 [23 18]] swaack first [popd] dip /
+                    0 23 [pop ++] . i [18] [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
+                             0 23 . pop ++ [18] [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
+                                0 . ++ [18] [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
+                                1 . [18] [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
+                           1 [18] . [pop ++] step [41.0 [23 18]] swaack first [popd] dip /
+                  1 [18] [pop ++] . step [41.0 [23 18]] swaack first [popd] dip /
+                    1 18 [pop ++] . i [41.0 [23 18]] swaack first [popd] dip /
+                             1 18 . pop ++ [41.0 [23 18]] swaack first [popd] dip /
+                                1 . ++ [41.0 [23 18]] swaack first [popd] dip /
+                                2 . [41.0 [23 18]] swaack first [popd] dip /
+                 2 [41.0 [23 18]] . swaack first [popd] dip /
+                 [23 18] 41.0 [2] . first [popd] dip /
+                   [23 18] 41.0 2 . [popd] dip /
+            [23 18] 41.0 2 [popd] . dip /
+                     [23 18] 41.0 . popd 2 /
+                             41.0 . 2 /
+                           41.0 2 . /
+                             20.5 .
+
+
+
+
+

Replacing size with a Python version

+

Both sum and size each convert a sequence to a single value.

+
 sum == 0 swap [+] step
+size == 0 swap [pop ++] step
+
+
+

An efficient sum function is already in the library. But for +size we can use a “compiled” version hand-written in Python to speed +up evaluation and make the trace more readable.

+
from joy.library import SimpleFunctionWrapper
+from joy.utils.stack import iter_stack
+
+
+@SimpleFunctionWrapper
+def size(stack):
+    '''Return the size of the sequence on the stack.'''
+    sequence, stack = stack
+    n = 0
+    for _ in iter_stack(sequence):
+        n += 1
+    return n, stack
+
+
+

Now we replace the old version in the dictionary with the new version, +and re-evaluate the expression.

+
D['size'] = size
+
+
+
+
+

A shorter trace

+

You can see that size now executes in a single step.

+
V('[23 18] average')
+
+
+
                                  . [23 18] average
+                          [23 18] . average
+                          [23 18] . [sum 1.0 *] [size] cleave /
+              [23 18] [sum 1.0 *] . [size] cleave /
+       [23 18] [sum 1.0 *] [size] . cleave /
+       [23 18] [sum 1.0 *] [size] . [i] app2 [popd] dip /
+   [23 18] [sum 1.0 *] [size] [i] . app2 [popd] dip /
+[23 18] [[sum 1.0 *] [23 18]] [i] . infra first [[size] [23 18]] [i] infra first [popd] dip /
+              [23 18] [sum 1.0 *] . i [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
+                          [23 18] . sum 1.0 * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
+                               41 . 1.0 * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
+                           41 1.0 . * [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
+                             41.0 . [[23 18]] swaack first [[size] [23 18]] [i] infra first [popd] dip /
+                   41.0 [[23 18]] . swaack first [[size] [23 18]] [i] infra first [popd] dip /
+                   [23 18] [41.0] . first [[size] [23 18]] [i] infra first [popd] dip /
+                     [23 18] 41.0 . [[size] [23 18]] [i] infra first [popd] dip /
+    [23 18] 41.0 [[size] [23 18]] . [i] infra first [popd] dip /
+[23 18] 41.0 [[size] [23 18]] [i] . infra first [popd] dip /
+                   [23 18] [size] . i [41.0 [23 18]] swaack first [popd] dip /
+                          [23 18] . size [41.0 [23 18]] swaack first [popd] dip /
+                                2 . [41.0 [23 18]] swaack first [popd] dip /
+                 2 [41.0 [23 18]] . swaack first [popd] dip /
+                 [23 18] 41.0 [2] . first [popd] dip /
+                   [23 18] 41.0 2 . [popd] dip /
+            [23 18] 41.0 2 [popd] . dip /
+                     [23 18] 41.0 . popd 2 /
+                             41.0 . 2 /
+                           41.0 2 . /
+                             20.5 .
+
+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/docs/sphinx_docs/_build/html/notebooks/Treestep.html b/docs/sphinx_docs/_build/html/notebooks/Treestep.html new file mode 100644 index 0000000..243f5e5 --- /dev/null +++ b/docs/sphinx_docs/_build/html/notebooks/Treestep.html @@ -0,0 +1,567 @@ + + + + + + + + Treating Trees II: treestep — Thun 0.2.0 documentation + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Treating Trees II: treestep

+

Let’s consider a tree structure, similar to one described “Why +functional programming matters” by John +Hughes, +that consists of a node value followed by zero or more child trees. (The +asterisk is meant to indicate the Kleene +star.)

+
tree = [] | [node tree*]
+
+
+

In the spirit of step we are going to define a combinator +treestep which expects a tree and three additional items: a +base-case function [B], and two quoted programs [N] and [C].

+
tree [B] [N] [C] treestep
+
+
+

If the current tree node is empty then just execute B:

+
   [] [B] [N] [C] treestep
+---------------------------
+   []  B
+
+
+

Otherwise, evaluate N on the node value, map the whole function +(abbreviated here as K) over the child trees recursively, and then +combine the result with C.

+
   [node tree*] [B] [N] [C] treestep
+--------------------------------------- w/ K == [B] [N] [C] treestep
+       node N [tree*] [K] map C
+
+
+

(Later on we’ll experiment with making map part of C so you can +use other combinators.)

+
+

Derive the recursive function.

+

We can begin to derive it by finding the ifte stage that genrec +will produce.

+
K == [not] [B] [R0]   [R1] genrec
+  == [not] [B] [R0 [K] R1] ifte
+
+
+

So we just have to derive J:

+
J == R0 [K] R1
+
+
+

The behavior of J is to accept a (non-empty) tree node and arrive at +the desired outcome.

+
       [node tree*] J
+------------------------------
+   node N [tree*] [K] map C
+
+
+

So J will have some form like:

+
J == ... [N] ... [K] ... [C] ...
+
+
+

Let’s dive in. First, unquote the node and dip N.

+
[node tree*] uncons [N] dip
+node [tree*]        [N] dip
+node N [tree*]
+
+
+

Next, map K over the child trees and combine with C.

+
node N [tree*] [K] map C
+node N [tree*] [K] map C
+node N [K.tree*]       C
+
+
+

So:

+
J == uncons [N] dip [K] map C
+
+
+

Plug it in and convert to genrec:

+
K == [not] [B] [J                       ] ifte
+  == [not] [B] [uncons [N] dip [K] map C] ifte
+  == [not] [B] [uncons [N] dip]   [map C] genrec
+
+
+
+
+

Extract the givens to parameterize the program.

+

Working backwards:

+
[not] [B]          [uncons [N] dip]                  [map C] genrec
+[B] [not] swap     [uncons [N] dip]                  [map C] genrec
+[B]                [uncons [N] dip] [[not] swap] dip [map C] genrec
+                                    ^^^^^^^^^^^^^^^^
+[B] [[N] dip]      [uncons] swoncat [[not] swap] dip [map C] genrec
+[B] [N] [dip] cons [uncons] swoncat [[not] swap] dip [map C] genrec
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+
+

Extract a couple of auxiliary definitions:

+
TS.0 == [[not] swap] dip
+TS.1 == [dip] cons [uncons] swoncat
+
+
+
[B] [N] TS.1 TS.0 [map C]                         genrec
+[B] [N]           [map C]         [TS.1 TS.0] dip genrec
+[B] [N] [C]         [map] swoncat [TS.1 TS.0] dip genrec
+
+
+

The givens are all to the left so we have our definition.

+
+

(alternate) Extract the givens to parameterize the program.

+

Working backwards:

+
[not] [B]           [uncons [N] dip]            [map C] genrec
+[not] [B] [N]       [dip] cons [uncons] swoncat [map C] genrec
+[B] [N] [not] roll> [dip] cons [uncons] swoncat [map C] genrec
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+
+
+
+
+

Define treestep

+
from notebook_preamble import D, J, V, define, DefinitionWrapper
+
+
+
DefinitionWrapper.add_definitions('''
+
+    _treestep_0 == [[not] swap] dip
+    _treestep_1 == [dip] cons [uncons] swoncat
+    treegrind == [_treestep_1 _treestep_0] dip genrec
+    treestep == [map] swoncat treegrind
+
+''', D)
+
+
+
+
+

Examples

+

Consider trees, the nodes of which are integers. We can find the sum of +all nodes in a tree with this function:

+
sumtree == [pop 0] [] [sum +] treestep
+
+
+
define('sumtree == [pop 0] [] [sum +] treestep')
+
+
+

Running this function on an empty tree value gives zero:

+
   [] [pop 0] [] [sum +] treestep
+------------------------------------
+           0
+
+
+
J('[] sumtree')  # Empty tree.
+
+
+
0
+
+
+

Running it on a non-empty node:

+
[n tree*]  [pop 0] [] [sum +] treestep
+n [tree*] [[pop 0] [] [sum +] treestep] map sum +
+n [ ... ]                                   sum +
+n m                                             +
+n+m
+
+
+
J('[23] sumtree')  # No child trees.
+
+
+
23
+
+
+
J('[23 []] sumtree')  # Child tree, empty.
+
+
+
23
+
+
+
J('[23 [2 [4]] [3]] sumtree')  # Non-empty child trees.
+
+
+
32
+
+
+
J('[23 [2 [8] [9]] [3] [4 []]] sumtree')  # Etc...
+
+
+
49
+
+
+
J('[23 [2 [8] [9]] [3] [4 []]] [pop 0] [] [cons sum] treestep')  # Alternate "spelling".
+
+
+
49
+
+
+
J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 23] [cons] treestep')  # Replace each node.
+
+
+
[23 [23 [23] [23]] [23] [23 []]]
+
+
+
J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 1] [cons] treestep')
+
+
+
[1 [1 [1] [1]] [1] [1 []]]
+
+
+
J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 1] [cons] treestep sumtree')
+
+
+
6
+
+
+
J('[23 [2 [8] [9]] [3] [4 []]] [pop 0] [pop 1] [sum +] treestep')  # Combine replace and sum into one function.
+
+
+
6
+
+
+
J('[4 [3 [] [7]]] [pop 0] [pop 1] [sum +] treestep')  # Combine replace and sum into one function.
+
+
+
3
+
+
+
+
+

Redefining the Ordered Binary Tree in terms of treestep.

+
Tree = [] | [[key value] left right]
+
+
+

What kind of functions can we write for this with our treestep?

+

The pattern for processing a non-empty node is:

+
node N [tree*] [K] map C
+
+
+

Plugging in our BTree structure:

+
[key value] N [left right] [K] map C
+
+
+
+

Traversal

+
[key value] first [left right] [K] map i
+key [value]       [left right] [K] map i
+key               [left right] [K] map i
+key               [lkey rkey ]         i
+key                lkey rkey
+
+
+

This doesn’t quite work:

+
J('[[3 0] [[2 0] [][]] [[9 0] [[5 0] [[4 0] [][]] [[8 0] [[6 0] [] [[7 0] [][]]][]]][]]] ["B"] [first] [i] treestep')
+
+
+
3 'B' 'B'
+
+
+

Doesn’t work because map extracts the first item of whatever its +mapped function produces. We have to return a list, rather than +depositing our results directly on the stack.

+
[key value] N     [left right] [K] map C
+
+[key value] first [left right] [K] map flatten cons
+key               [left right] [K] map flatten cons
+key               [[lk] [rk] ]         flatten cons
+key               [ lk   rk  ]                 cons
+                  [key  lk   rk  ]
+
+
+

So:

+
[] [first] [flatten cons] treestep
+
+
+
J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]]   [] [first] [flatten cons] treestep')
+
+
+
[3 2 9 5 4 8 6 7]
+
+
+

There we go.

+
+
+

In-order traversal

+

From here:

+
key [[lk] [rk]] C
+key [[lk] [rk]] i
+key  [lk] [rk] roll<
+[lk] [rk] key swons concat
+[lk] [key rk]       concat
+[lk   key rk]
+
+
+

So:

+
[] [i roll< swons concat] [first] treestep
+
+
+
J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]]   [] [uncons pop] [i roll< swons concat] treestep')
+
+
+
[2 3 4 5 6 7 8 9]
+
+
+
+
+
+

With treegrind?

+

The treegrind function doesn’t include the map combinator, so +the [C] function must arrange to use some combinator on the quoted +recursive copy [K]. With this function, the pattern for processing a +non-empty node is:

+
node N [tree*] [K] C
+
+
+

Plugging in our BTree structure:

+
[key value] N [left right] [K] C
+
+
+
J('[["key" "value"] ["left"] ["right"] ] ["B"] ["N"] ["C"] treegrind')
+
+
+
['key' 'value'] 'N' [['left'] ['right']] [[not] ['B'] [uncons ['N'] dip] ['C'] genrec] 'C'
+
+
+
+
+

treegrind with step

+

Iteration through the nodes

+
J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]]   [pop] ["N"] [step] treegrind')
+
+
+
[3 0] 'N' [2 0] 'N' [9 0] 'N' [5 0] 'N' [4 0] 'N' [8 0] 'N' [6 0] 'N' [7 0] 'N'
+
+
+

Sum the nodes’ keys.

+
J('0 [[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]]   [pop] [first +] [step] treegrind')
+
+
+
44
+
+
+

Rebuild the tree using map (imitating treestep.)

+
J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]]   [] [[100 +] infra] [map cons] treegrind')
+
+
+
[[103 0] [[102 0] [] []] [[109 0] [[105 0] [[104 0] [] []] [[108 0] [[106 0] [] [[107 0] [] []]] []]] []]]
+
+
+
+
+

Do we have the flexibility to reimplement Tree-get?

+

I think we do:

+
[B] [N] [C] treegrind
+
+
+

We’ll start by saying that the base-case (the key is not in the tree) is +user defined, and the per-node function is just the query key literal:

+
[B] [query_key] [C] treegrind
+
+
+

This means we just have to define C from:

+
[key value] query_key [left right] [K] C
+
+
+

Let’s try cmp:

+
C == P [T>] [E] [T<] cmp
+
+[key value] query_key [left right] [K] P [T>] [E] [T<] cmp
+
+
+
+

The predicate P

+

Seems pretty easy (we must preserve the value in case the keys are +equal):

+
[key value] query_key [left right] [K] P
+[key value] query_key [left right] [K] roll<
+[key value] [left right] [K] query_key       [roll< uncons swap] dip
+
+[key value] [left right] [K] roll< uncons swap query_key
+[left right] [K] [key value]       uncons swap query_key
+[left right] [K] key [value]              swap query_key
+[left right] [K] [value] key                   query_key
+
+P == roll< [roll< uncons swap] dip
+
+
+

(Possibly with a swap at the end? Or just swap T< and T>.)

+

So now:

+
[left right] [K] [value] key query_key [T>] [E] [T<] cmp
+
+
+

Becomes one of these three:

+
[left right] [K] [value] T>
+[left right] [K] [value] E
+[left right] [K] [value] T<
+
+
+
+
+

E

+

Easy.

+
E == roll> popop first
+
+
+
+
+

T< and T>

+
T< == pop [first] dip i
+T> == pop [second] dip i
+
+
+
+
+
+

Putting it together

+
T> == pop [first] dip i
+T< == pop [second] dip i
+E == roll> popop first
+P == roll< [roll< uncons swap] dip
+
+Tree-get == [P [T>] [E] [T<] cmp] treegrind
+
+
+

To me, that seems simpler than the genrec version.

+
DefinitionWrapper.add_definitions('''
+
+    T> == pop [first] dip i
+    T< == pop [second] dip i
+    E == roll> popop first
+    P == roll< [roll< uncons swap] dip
+
+    Tree-get == [P [T>] [E] [T<] cmp] treegrind
+
+''', D)
+
+
+
J('''\
+
+[[3 13] [[2 12] [] []] [[9 19] [[5 15] [[4 14] [] []] [[8 18] [[6 16] [] [[7 17] [] []]] []]] []]]
+
+[] [5] Tree-get
+
+''')
+
+
+
15
+
+
+
J('''\
+
+[[3 13] [[2 12] [] []] [[9 19] [[5 15] [[4 14] [] []] [[8 18] [[6 16] [] [[7 17] [] []]] []]] []]]
+
+[pop "nope"] [25] Tree-get
+
+''')
+
+
+
'nope'
+
+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/docs/sphinx_docs/_build/html/notebooks/Types.html b/docs/sphinx_docs/_build/html/notebooks/Types.html new file mode 100644 index 0000000..2288a36 --- /dev/null +++ b/docs/sphinx_docs/_build/html/notebooks/Types.html @@ -0,0 +1,2759 @@ + + + + + + + + Type Inference — Thun 0.2.0 documentation + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Type Inference

+

This notebook presents a simple type inferencer for Joy code. It can +infer the stack effect of most Joy expressions. It built largely by +means of existing ideas and research (some of it may be original but I’m +not able to say, as I don’t fully understand the all of the source +material in the depth required to make that call.) A great overview of +the existing knowledge is a talk “Type Inference in Stack-Based +Programming +Languages” +given by Rob Kleffner on or about 2017-03-10 as part of a course on the +history of programming languages.

+

The notebook starts with a simple inferencer based on the work of Jaanus +Pöial which we then progressively elaborate to cover more Joy semantics. +Along the way we write a simple “compiler” that emits Python code for +what I like to call Yin functions.

+

Yin functions are those that only rearrange values in stacks, as opposed +to Yang functions that actually work on the values themselves. It’s +interesting to note that a Joy with only stacks (no other kinds of +values) can be made and is Turing-complete, therefore all Yang functions +are actually Yin functions, and all computation can be performed by +manipulations of structures of containers, which is a restatement of the +Laws of Form. (Also, this implies that every program can be put into a +form such that it can be computed in a single step, although that step +may be enormous or unending.)

+

Although I haven’t completed it yet, a Joy based on Laws of Form +provides the foundation for a provably correct computing system “down to +the metal”. This is my original and still primary motivation for +developing Joy. (I want a proven-correct Operating System for a swarm of +trash-collecting recycler robots. To trust it I have to implementment it +myself from first principles, and I’m not smart enough to truly grok the +existing literature and software, so I had to go look for and find LoF +and Joy. Now that I have the mental tools to build my robot OS I can get +down to it.

+

Anyhow, here’s type inference…

+
+

Part I: Pöial’s Rules

+

“Typing Tools for Typeless Stack Languages” by Jaanus +Pöial

+
@INPROCEEDINGS{Pöial06typingtools,
+    author = {Jaanus Pöial},
+    title = {Typing tools for typeless stack languages},
+    booktitle = {In 23rd Euro-Forth Conference},
+    year = {2006},
+    pages = {40--46}
+}
+
+
+
+

First Rule

+

This rule deals with functions (and literals) that put items on the +stack (-- d):

+
   (a -- b)∘(-- d)
+---------------------
+     (a -- b d)
+
+
+
+
+

Second Rule

+

This rule deals with functions that consume items from the stack +(a --):

+
   (a --)∘(c -- d)
+---------------------
+     (c a -- d)
+
+
+
+
+

Third Rule

+

The third rule is actually two rules. These two rules deal with +composing functions when the second one will consume one of items the +first one produces. The two types must be +*unified* +or a type conflict declared.

+
   (a -- b t[i])∘(c u[j] -- d)   t <= u (t is subtype of u)
+-------------------------------
+   (a -- b     )∘(c      -- d)   t[i] == t[k] == u[j]
+                                         ^
+
+   (a -- b t[i])∘(c u[j] -- d)   u <= t (u is subtype of t)
+-------------------------------
+   (a -- b     )∘(c      -- d)   t[i] == u[k] == u[j]
+
+
+

Let’s work through some examples by hand to develop an intuition for the +algorithm.

+

There’s a function in one of the other notebooks.

+
F == pop swap roll< rest rest cons cons
+
+
+

It’s all “stack chatter” and list manipulation so we should be able to +deduce its type.

+
+
+

Stack Effect Comments

+

Joy function types will be represented by Forth-style stack effect +comments. I’m going to use numbers instead of names to keep track of the +stack arguments. (A little bit like De Bruijn +index, at least it +reminds me of them):

+
pop (1 --)
+
+swap (1 2 -- 2 1)
+
+roll< (1 2 3 -- 2 3 1)
+
+
+

These commands alter the stack but don’t “look at” the values so these +numbers represent an “Any type”.

+
+
+

pop swap

+
(1 --) (1 2 -- 2 1)
+
+
+

Here we encounter a complication. The argument numbers need to be made +unique among both sides. For this let’s change pop to use 0:

+
(0 --) (1 2 -- 2 1)
+
+
+

Following the second rule:

+
(1 2 0 -- 2 1)
+
+
+
+
+

pop∘swap roll<

+
(1 2 0 -- 2 1) (1 2 3 -- 2 3 1)
+
+
+

Let’s re-label them:

+
(1a 2a 0a -- 2a 1a) (1b 2b 3b -- 2b 3b 1b)
+
+
+

Now we follow the rules.

+

We must unify 1a and 3b, and 2a and 2b, replacing the +terms in the forms:

+
(1a 2a 0a -- 2a 1a) (1b 2b 3b -- 2b 3b 1b)
+                                            w/  {1a: 3b}
+(3b 2a 0a -- 2a   ) (1b 2b    -- 2b 3b 1b)
+                                            w/  {2a: 2b}
+(3b 2b 0a --      ) (1b       -- 2b 3b 1b)
+
+
+

Here we must apply the second rule:

+
   (3b 2b 0a --) (1b -- 2b 3b 1b)
+-----------------------------------
+     (1b 3b 2b 0a -- 2b 3b 1b)
+
+
+

Now we de-label the type, uh, labels:

+
(1b 3b 2b 0a -- 2b 3b 1b)
+
+w/ {
+    1b: 1,
+    3b: 2,
+    2b: 3,
+    0a: 0,
+    }
+
+(1 2 3 0 -- 3 2 1)
+
+
+

And now we have the stack effect comment for pop∘swap∘roll<.

+
+
+

Compiling pop∘swap∘roll<

+

The simplest way to “compile” this function would be something like:

+
def poswrd(s, e, d):
+    return rolldown(*swap(*pop(s, e, d)))
+
+
+

However, internally this function would still be allocating tuples +(stack cells) and doing other unnecesssary work.

+

Looking ahead for a moment, from the stack effect comment:

+
(1 2 3 0 -- 3 2 1)
+
+
+

We should be able to directly write out a Python function like:

+
def poswrd(stack):
+    (_, (a, (b, (c, stack)))) = stack
+    return (c, (b, (a, stack)))
+
+
+

This eliminates the internal work of the first version. Because this +function only rearranges the stack and doesn’t do any actual processing +on the stack items themselves all the information needed to implement it +is in the stack effect comment.

+
+
+

Functions on Lists

+

These are slightly tricky.

+
rest ( [1 ...] -- [...] )
+
+cons ( 1 [...] -- [1 ...] )
+
+
+
+
+

pop∘swap∘roll< rest

+
(1 2 3 0 -- 3 2 1) ([1 ...] -- [...])
+
+
+

Re-label (instead of adding left and right tags I’m just taking the next +available index number for the right-side stack effect comment):

+
(1 2 3 0 -- 3 2 1) ([4 ...] -- [...])
+
+
+

Unify and update:

+
(1       2 3 0 -- 3 2 1) ([4 ...] -- [...])
+                                             w/ {1: [4 ...]}
+([4 ...] 2 3 0 -- 3 2  ) (        -- [...])
+
+
+

Apply the first rule:

+
   ([4 ...] 2 3 0 -- 3 2) (-- [...])
+---------------------------------------
+     ([4 ...] 2 3 0 -- 3 2 [...])
+
+
+

And there we are.

+
+
+

pop∘swap∘roll<∘rest rest

+

Let’s do it again.

+
([4 ...] 2 3 0 -- 3 2 [...]) ([1 ...] -- [...])
+
+
+

Re-label (the tails of the lists on each side each get their own label):

+
([4 .0.] 2 3 0 -- 3 2 [.0.]) ([5 .1.] -- [.1.])
+
+
+

Unify and update (note the opening square brackets have been omited in +the substitution dict, this is deliberate and I’ll explain below):

+
([4 .0.]   2 3 0 -- 3 2 [.0.]  ) ([5 .1.] -- [.1.])
+                                                    w/ { .0.] : 5 .1.] }
+([4 5 .1.] 2 3 0 -- 3 2 [5 .1.]) ([5 .1.] -- [.1.])
+
+
+

How do we find .0.] in [4 .0.] and replace it with 5 .1.] +getting the result [4 5 .1.]? This might seem hard, but because the +underlying structure of the Joy list is a cons-list in Python it’s +actually pretty easy. I’ll explain below.

+

Next we unify and find our two terms are the same already: [5 .1.]:

+
([4 5 .1.] 2 3 0 -- 3 2 [5 .1.]) ([5 .1.] -- [.1.])
+
+
+

Giving us:

+
([4 5 .1.] 2 3 0 -- 3 2) (-- [.1.])
+
+
+

From here we apply the first rule and get:

+
([4 5 .1.] 2 3 0 -- 3 2 [.1.])
+
+
+

Cleaning up the labels:

+
([4 5 ...] 2 3 1 -- 3 2 [...])
+
+
+

This is the stack effect of pop∘swap∘roll<∘rest∘rest.

+
+
+

pop∘swap∘roll<∘rest∘rest cons

+
([4 5 ...] 2 3 1 -- 3 2 [...]) (1 [...] -- [1 ...])
+
+
+

Re-label:

+
([4 5 .1.] 2 3 1 -- 3 2 [.1.]) (6 [.2.] -- [6 .2.])
+
+
+

Unify:

+
([4 5 .1.] 2 3 1 -- 3 2 [.1.]) (6 [.2.] -- [6 .2.])
+                                                     w/ { .1.] : .2.] }
+([4 5 .2.] 2 3 1 -- 3 2      ) (6       -- [6 .2.])
+                                                     w/ {2: 6}
+([4 5 .2.] 6 3 1 -- 3        ) (        -- [6 .2.])
+
+
+

First rule:

+
([4 5 .2.] 6 3 1 -- 3 [6 .2.])
+
+
+

Re-label:

+
([4 5 ...] 2 3 1 -- 3 [2 ...])
+
+
+

Done.

+
+
+

pop∘swap∘roll<∘rest∘rest∘cons cons

+

One more time.

+
([4 5 ...] 2 3 1 -- 3 [2 ...]) (1 [...] -- [1 ...])
+
+
+

Re-label:

+
([4 5 .1.] 2 3 1 -- 3 [2 .1.]) (6 [.2.] -- [6 .2.])
+
+
+

Unify:

+
([4 5 .1.] 2 3 1 -- 3 [2 .1.]) (6 [.2.] -- [6 .2.]  )
+                                                       w/ { .2.] : 2 .1.] }
+([4 5 .1.] 2 3 1 -- 3        ) (6       -- [6 2 .1.])
+                                                       w/ {3: 6}
+([4 5 .1.] 2 6 1 --          ) (        -- [6 2 .1.])
+
+
+

First or second rule:

+
([4 5 .1.] 2 6 1 -- [6 2 .1.])
+
+
+

Clean up the labels:

+
([4 5 ...] 2 3 1 -- [3 2 ...])
+
+
+

And there you have it, the stack effect for +pop∘swap∘roll<∘rest∘rest∘cons∘cons.

+
([4 5 ...] 2 3 1 -- [3 2 ...])
+
+
+

From this stack effect comment it should be possible to construct the +following Python code:

+
def F(stack):
+    (_, (d, (c, ((a, (b, S0)), stack)))) = stack
+    return (d, (c, S0)), stack
+
+
+
+
+
+

Part II: Implementation

+
+

Representing Stack Effect Comments in Python

+

I’m going to use pairs of tuples of type descriptors, which will be +integers or tuples of type descriptors:

+
roll_dn = (1, 2, 3), (2, 3, 1)
+
+pop = (1,), ()
+
+swap = (1, 2), (2, 1)
+
+
+
+
+

compose()

+
def compose(f, g):
+
+    (f_in, f_out), (g_in, g_out) = f, g
+
+    # First rule.
+    #
+    #       (a -- b) (-- d)
+    #    ---------------------
+    #         (a -- b d)
+
+    if not g_in:
+
+        fg_in, fg_out = f_in, f_out + g_out
+
+    # Second rule.
+    #
+    #       (a --) (c -- d)
+    #    ---------------------
+    #         (c a -- d)
+
+    elif not f_out:
+
+        fg_in, fg_out = g_in + f_in, g_out
+
+    else: # Unify, update, recur.
+
+        fo, gi = f_out[-1], g_in[-1]
+
+        s = unify(gi, fo)
+
+        if s == False:  # s can also be the empty dict, which is ok.
+            raise TypeError('Cannot unify %r and %r.' % (fo, gi))
+
+        f_g = (f_in, f_out[:-1]), (g_in[:-1], g_out)
+
+        if s: f_g = update(s, f_g)
+
+        fg_in, fg_out = compose(*f_g)
+
+    return fg_in, fg_out
+
+
+
+
+

unify()

+
def unify(u, v, s=None):
+    if s is None:
+        s = {}
+
+    if u == v:
+        return s
+
+    if isinstance(u, int):
+        s[u] = v
+        return s
+
+    if isinstance(v, int):
+        s[v] = u
+        return s
+
+    return False
+
+
+
+
+

update()

+
def update(s, term):
+    if not isinstance(term, tuple):
+        return s.get(term, term)
+    return tuple(update(s, inner) for inner in term)
+
+
+
+
+

relabel()

+
def relabel(left, right):
+    return left, _1000(right)
+
+def _1000(right):
+    if not isinstance(right, tuple):
+        return 1000 + right
+    return tuple(_1000(n) for n in right)
+
+relabel(pop, swap)
+
+
+
(((1,), ()), ((1001, 1002), (1002, 1001)))
+
+
+
+
+

delabel()

+
def delabel(f):
+    s = {u: i for i, u in enumerate(sorted(_unique(f)))}
+    return update(s, f)
+
+def _unique(f, seen=None):
+    if seen is None:
+        seen = set()
+    if not isinstance(f, tuple):
+        seen.add(f)
+    else:
+        for inner in f:
+            _unique(inner, seen)
+    return seen
+
+delabel(relabel(pop, swap))
+
+
+
(((0,), ()), ((1, 2), (2, 1)))
+
+
+
+
+

C()

+

At last we put it all together in a function C() that accepts two +stack effect comments and returns their composition (or raises and +exception if they can’t be composed due to type conflicts.)

+
def C(f, g):
+    f, g = relabel(f, g)
+    fg = compose(f, g)
+    return delabel(fg)
+
+
+

Let’s try it out.

+
C(pop, swap)
+
+
+
((1, 2, 0), (2, 1))
+
+
+
C(C(pop, swap), roll_dn)
+
+
+
((3, 1, 2, 0), (2, 1, 3))
+
+
+
C(swap, roll_dn)
+
+
+
((2, 0, 1), (1, 0, 2))
+
+
+
C(pop, C(swap, roll_dn))
+
+
+
((3, 1, 2, 0), (2, 1, 3))
+
+
+
poswrd = reduce(C, (pop, swap, roll_dn))
+poswrd
+
+
+
((3, 1, 2, 0), (2, 1, 3))
+
+
+
+
+

Stack Functions

+

Here’s that trick to represent functions like rest and cons that +manipulate stacks. We use a cons-list of tuples and give the tails their +own numbers. Then everything above already works.

+
rest = ((1, 2),), (2,)
+
+cons = (1, 2), ((1, 2),)
+
+
+
C(poswrd, rest)
+
+
+
(((3, 4), 1, 2, 0), (2, 1, 4))
+
+
+

Compare this to the stack effect comment we wrote above:

+
((  (3, 4), 1, 2, 0 ), ( 2, 1,   4  ))
+(   [4 ...] 2  3  0  --  3  2  [...])
+
+
+

The translation table, if you will, would be:

+
{
+3: 4,
+4: ...],
+1: 2,
+2: 3,
+0: 0,
+}
+
+
+
F = reduce(C, (pop, swap, roll_dn, rest, rest, cons, cons))
+
+F
+
+
+
(((3, (4, 5)), 1, 2, 0), ((2, (1, 5)),))
+
+
+

Compare with the stack effect comment and you can see it works fine:

+
([4 5 ...] 2 3 1 -- [3 2 ...])
+  3 4  5   1 2 0     2 1  5
+
+
+
+
+

Dealing with cons and uncons

+

However, if we try to compose e.g. cons and uncons it won’t +work:

+
uncons = ((1, 2),), (1, 2)
+
+
+
try:
+    C(cons, uncons)
+except Exception, e:
+    print e
+
+
+
Cannot unify (1, 2) and (1001, 1002).
+
+
+
+

unify() version 2

+

The problem is that the unify() function as written doesn’t handle +the case when both terms are tuples. We just have to add a clause to +deal with this recursively:

+
def unify(u, v, s=None):
+    if s is None:
+        s = {}
+    elif s:
+        u = update(s, u)
+        v = update(s, v)
+
+    if u == v:
+        return s
+
+    if isinstance(u, int):
+        s[u] = v
+        return s
+
+    if isinstance(v, int):
+        s[v] = u
+        return s
+
+    if isinstance(u, tuple) and isinstance(v, tuple):
+        if len(u) != len(v) != 2:
+            raise ValueError(repr((u, v)))
+        for uu, vv in zip(u, v):
+            s = unify(uu, vv, s)
+            if s == False: # (instead of a substitution dict.)
+                break
+        return s
+
+    return False
+
+
+
C(cons, uncons)
+
+
+
((0, 1), (0, 1))
+
+
+
+
+
+
+

Part III: Compiling Yin Functions

+

Now consider the Python function we would like to derive:

+
def F_python(stack):
+    (_, (d, (c, ((a, (b, S0)), stack)))) = stack
+    return (d, (c, S0)), stack
+
+
+

And compare it to the input stack effect comment tuple we just computed:

+
F[0]
+
+
+
((3, (4, 5)), 1, 2, 0)
+
+
+

The stack-de-structuring tuple has nearly the same form as our input +stack effect comment tuple, just in the reverse order:

+
(_, (d, (c, ((a, (b, S0)), stack))))
+
+
+

Remove the punctuation:

+
_   d   c   (a, (b, S0))
+
+
+

Reverse the order and compare:

+
 (a, (b, S0))   c   d   _
+((3, (4, 5 )),  1,  2,  0)
+
+
+

Eh?

+

And the return tuple

+
F[1]
+
+
+
((2, (1, 5)),)
+
+
+

is similar to the output stack effect comment tuple:

+
((d, (c, S0)), stack)
+((2, (1, 5 )),      )
+
+
+

This should make it pretty easy to write a Python function that accepts +the stack effect comment tuples and returns a new Python function +(either as a string of code or a function object ready to use) that +performs the semantics of that Joy function (described by the stack +effect.)

+
+

Python Identifiers

+

We want to substitute Python identifiers for the integers. I’m going to +repurpose joy.parser.Symbol class for this:

+
from collections import defaultdict
+from joy.parser import Symbol
+
+
+def _names_for():
+    I = iter(xrange(1000))
+    return lambda: Symbol('a%i' % next(I))
+
+
+def identifiers(term, s=None):
+    if s is None:
+        s = defaultdict(_names_for())
+    if isinstance(term, int):
+        return s[term]
+    return tuple(identifiers(inner, s) for inner in term)
+
+
+
+
+

doc_from_stack_effect()

+

As a convenience I’ve implemented a function to convert the Python stack +effect comment tuples to reasonable text format. There are some details +in how this code works that related to stuff later in the notebook, so +you should skip it for now and read it later if you’re interested.

+
def doc_from_stack_effect(inputs, outputs):
+    return '(%s--%s)' % (
+        ' '.join(map(_to_str, inputs + ('',))),
+        ' '.join(map(_to_str, ('',) + outputs))
+    )
+
+
+def _to_str(term):
+    if not isinstance(term, tuple):
+        try:
+            t = term.prefix == 's'
+        except AttributeError:
+            return str(term)
+        return '[.%i.]' % term.number if t else str(term)
+
+    a = []
+    while term and isinstance(term, tuple):
+        item, term = term
+        a.append(_to_str(item))
+
+    try:
+        n = term.number
+    except AttributeError:
+        n = term
+    else:
+        if term.prefix != 's':
+            raise ValueError('Stack label: %s' % (term,))
+
+    a.append('.%s.' % (n,))
+    return '[%s]' % ' '.join(a)
+
+
+
+
+

compile_()

+

Now we can write a compiler function to emit Python source code. (The +underscore suffix distiguishes it from the built-in compile() +function.)

+
def compile_(name, f, doc=None):
+    if doc is None:
+        doc = doc_from_stack_effect(*f)
+    inputs, outputs = identifiers(f)
+    i = o = Symbol('stack')
+    for term in inputs:
+        i = term, i
+    for term in outputs:
+        o = term, o
+    return '''def %s(stack):
+    """%s"""
+    %s = stack
+    return %s''' % (name, doc, i, o)
+
+
+

Here it is in action:

+
source = compile_('F', F)
+
+print source
+
+
+
def F(stack):
+    """([3 4 .5.] 1 2 0 -- [2 1 .5.])"""
+    (a5, (a4, (a3, ((a0, (a1, a2)), stack)))) = stack
+    return ((a4, (a3, a2)), stack)
+
+
+

Compare:

+
def F_python(stack):
+    (_, (d, (c, ((a, (b, S0)), stack)))) = stack
+    return ((d, (c, S0)), stack)
+
+
+

Next steps:

+
L = {}
+
+eval(compile(source, '__main__', 'single'), {}, L)
+
+L['F']
+
+
+
<function F>
+
+
+

Let’s try it out:

+
from notebook_preamble import D, J, V
+from joy.library import SimpleFunctionWrapper
+
+
+
D['F'] = SimpleFunctionWrapper(L['F'])
+
+
+
J('[4 5 ...] 2 3 1 F')
+
+
+
[3 2 ...]
+
+
+

With this, we have a partial Joy compiler that works on the subset of +Joy functions that manipulate stacks (both what I call “stack chatter” +and the ones that manipulate stacks on the stack.)

+

I’m probably going to modify the definition wrapper code to detect +definitions that can be compiled by this partial compiler and do it +automatically. It might be a reasonable idea to detect sequences of +compilable functions in definitions that have uncompilable functions in +them and just compile those. However, if your library is well-factored +this might be less helpful.

+
+
+

Compiling Library Functions

+

We can use compile_() to generate many primitives in the library +from their stack effect comments:

+
def defs():
+
+    rolldown = (1, 2, 3), (2, 3, 1)
+
+    rollup = (1, 2, 3), (3, 1, 2)
+
+    pop = (1,), ()
+
+    swap = (1, 2), (2, 1)
+
+    rest = ((1, 2),), (2,)
+
+    rrest = C(rest, rest)
+
+    cons = (1, 2), ((1, 2),)
+
+    uncons = ((1, 2),), (1, 2)
+
+    swons = C(swap, cons)
+
+    return locals()
+
+
+
for name, stack_effect_comment in sorted(defs().items()):
+    print
+    print compile_(name, stack_effect_comment)
+    print
+
+
+
def cons(stack):
+    """(1 2 -- [1 .2.])"""
+    (a1, (a0, stack)) = stack
+    return ((a0, a1), stack)
+
+
+def pop(stack):
+    """(1 --)"""
+    (a0, stack) = stack
+    return stack
+
+
+def rest(stack):
+    """([1 .2.] -- 2)"""
+    ((a0, a1), stack) = stack
+    return (a1, stack)
+
+
+def rolldown(stack):
+    """(1 2 3 -- 2 3 1)"""
+    (a2, (a1, (a0, stack))) = stack
+    return (a0, (a2, (a1, stack)))
+
+
+def rollup(stack):
+    """(1 2 3 -- 3 1 2)"""
+    (a2, (a1, (a0, stack))) = stack
+    return (a1, (a0, (a2, stack)))
+
+
+def rrest(stack):
+    """([0 1 .2.] -- 2)"""
+    ((a0, (a1, a2)), stack) = stack
+    return (a2, stack)
+
+
+def swap(stack):
+    """(1 2 -- 2 1)"""
+    (a1, (a0, stack)) = stack
+    return (a0, (a1, stack))
+
+
+def swons(stack):
+    """(0 1 -- [1 .0.])"""
+    (a1, (a0, stack)) = stack
+    return ((a1, a0), stack)
+
+
+def uncons(stack):
+    """([1 .2.] -- 1 2)"""
+    ((a0, a1), stack) = stack
+    return (a1, (a0, stack))
+
+
+
+
+
+

Part IV: Types and Subtypes of Arguments

+

So far we have dealt with types of functions, those dealing with simple +stack manipulation. Let’s extend our machinery to deal with types of +arguments.

+
+

“Number” Type

+

Consider the definition of sqr:

+
sqr == dup mul
+
+
+

The dup function accepts one anything and returns two of that:

+
dup (1 -- 1 1)
+
+
+

And mul accepts two “numbers” (we’re ignoring ints vs. floats vs. +complex, etc., for now) and returns just one:

+
mul (n n -- n)
+
+
+

So we’re composing:

+
(1 -- 1 1)∘(n n -- n)
+
+
+

The rules say we unify 1 with n:

+
   (1 -- 1 1)∘(n n -- n)
+---------------------------  w/  {1: n}
+   (1 -- 1  )∘(n   -- n)
+
+
+

This involves detecting that “Any type” arguments can accept “numbers”. +If we were composing these functions the other way round this is still +the case:

+
   (n n -- n)∘(1 -- 1 1)
+---------------------------  w/  {1: n}
+   (n n --  )∘(  -- n n)
+
+
+

The important thing here is that the mapping is going the same way in +both cases, from the “any” integer to the number

+
+
+

Distinguishing Numbers

+

We should also mind that the number that mul produces is not +(necessarily) the same as either of its inputs, which are not +(necessarily) the same as each other:

+
mul (n2 n1 -- n3)
+
+
+   (1  -- 1  1)∘(n2 n1 -- n3)
+--------------------------------  w/  {1: n2}
+   (n2 -- n2  )∘(n2    -- n3)
+
+
+   (n2 n1 -- n3)∘(1 -- 1  1 )
+--------------------------------  w/  {1: n3}
+   (n2 n1 --   )∘(  -- n3 n3)
+
+
+
+
+

Distinguishing Types

+

So we need separate domains of “any” numbers and “number” numbers, and +we need to be able to ask the order of these domains. Now the notes on +the right side of rule three make more sense, eh?

+
   (a -- b t[i])∘(c u[j] -- d)   t <= u (t is subtype of u)
+-------------------------------
+   (a -- b     )∘(c      -- d)   t[i] == t[k] == u[j]
+                                         ^
+
+   (a -- b t[i])∘(c u[j] -- d)   u <= t (u is subtype of t)
+-------------------------------
+   (a -- b     )∘(c      -- d)   t[i] == u[k] == u[j]
+
+
+

The indices i, k, and j are the number part of our labels +and t and u are the domains.

+

By creative use of Python’s “double underscore” methods we can define a +Python class hierarchy of Joy types and use the issubclass() method +to establish domain ordering, as well as other handy behaviour that will +make it fairly easy to reuse most of the code above.

+
class AnyJoyType(object):
+
+    prefix = 'a'
+
+    def __init__(self, number):
+        self.number = number
+
+    def __repr__(self):
+        return self.prefix + str(self.number)
+
+    def __eq__(self, other):
+        return (
+            isinstance(other, self.__class__)
+            and other.prefix == self.prefix
+            and other.number == self.number
+        )
+
+    def __ge__(self, other):
+        return issubclass(other.__class__, self.__class__)
+
+    def __add__(self, other):
+        return self.__class__(self.number + other)
+    __radd__ = __add__
+
+    def __hash__(self):
+        return hash(repr(self))
+
+
+class NumberJoyType(AnyJoyType): prefix = 'n'
+class FloatJoyType(NumberJoyType): prefix = 'f'
+class IntJoyType(FloatJoyType): prefix = 'i'
+
+
+class StackJoyType(AnyJoyType):
+    prefix = 's'
+
+
+_R = range(10)
+A = map(AnyJoyType, _R)
+N = map(NumberJoyType, _R)
+S = map(StackJoyType, _R)
+
+
+

Mess with it a little:

+
from itertools import permutations
+
+
+

“Any” types can be specialized to numbers and stacks, but not vice +versa:

+
for a, b in permutations((A[0], N[0], S[0]), 2):
+    print a, '>=', b, '->', a >= b
+
+
+
a0 >= n0 -> True
+a0 >= s0 -> True
+n0 >= a0 -> False
+n0 >= s0 -> False
+s0 >= a0 -> False
+s0 >= n0 -> False
+
+
+

Our crude Numerical +Tower of numbers > +floats > integers works as well (but we’re not going to use it yet):

+
for a, b in permutations((A[0], N[0], FloatJoyType(0), IntJoyType(0)), 2):
+    print a, '>=', b, '->', a >= b
+
+
+
a0 >= n0 -> True
+a0 >= f0 -> True
+a0 >= i0 -> True
+n0 >= a0 -> False
+n0 >= f0 -> True
+n0 >= i0 -> True
+f0 >= a0 -> False
+f0 >= n0 -> False
+f0 >= i0 -> True
+i0 >= a0 -> False
+i0 >= n0 -> False
+i0 >= f0 -> False
+
+
+
+
+

Typing sqr

+
dup = (A[1],), (A[1], A[1])
+
+mul = (N[1], N[2]), (N[3],)
+
+
+
dup
+
+
+
((a1,), (a1, a1))
+
+
+
mul
+
+
+
((n1, n2), (n3,))
+
+
+
+
+

Modifying the Inferencer

+

Re-labeling still works fine:

+
foo = relabel(dup, mul)
+
+foo
+
+
+
(((a1,), (a1, a1)), ((n1001, n1002), (n1003,)))
+
+
+
+

delabel() version 2

+

The delabel() function needs an overhaul. It now has to keep track +of how many labels of each domain it has “seen”.

+
from collections import Counter
+
+
+def delabel(f, seen=None, c=None):
+    if seen is None:
+        assert c is None
+        seen, c = {}, Counter()
+
+    try:
+        return seen[f]
+    except KeyError:
+        pass
+
+    if not isinstance(f, tuple):
+        seen[f] = f.__class__(c[f.prefix])
+        c[f.prefix] += 1
+        return seen[f]
+
+    return tuple(delabel(inner, seen, c) for inner in f)
+
+
+
delabel(foo)
+
+
+
(((a0,), (a0, a0)), ((n0, n1), (n2,)))
+
+
+
+
+

unify() version 3

+
def unify(u, v, s=None):
+    if s is None:
+        s = {}
+    elif s:
+        u = update(s, u)
+        v = update(s, v)
+
+    if u == v:
+        return s
+
+    if isinstance(u, AnyJoyType) and isinstance(v, AnyJoyType):
+        if u >= v:
+            s[u] = v
+            return s
+        if v >= u:
+            s[v] = u
+            return s
+        raise TypeError('Cannot unify %r and %r.' % (u, v))
+
+    if isinstance(u, tuple) and isinstance(v, tuple):
+        if len(u) != len(v) != 2:
+            raise TypeError(repr((u, v)))
+        for uu, vv in zip(u, v):
+            s = unify(uu, vv, s)
+            if s == False: # (instead of a substitution dict.)
+                break
+        return s
+
+    if isinstance(v, tuple):
+        if not stacky(u):
+            raise TypeError('Cannot unify %r and %r.' % (u, v))
+        s[u] = v
+        return s
+
+    if isinstance(u, tuple):
+        if not stacky(v):
+            raise TypeError('Cannot unify %r and %r.' % (v, u))
+        s[v] = u
+        return s
+
+    return False
+
+
+def stacky(thing):
+    return thing.__class__ in {AnyJoyType, StackJoyType}
+
+
+

Rewrite the stack effect comments:

+
def defs():
+
+    rolldown = (A[1], A[2], A[3]), (A[2], A[3], A[1])
+
+    rollup = (A[1], A[2], A[3]), (A[3], A[1], A[2])
+
+    pop = (A[1],), ()
+
+    popop = (A[2], A[1],), ()
+
+    popd = (A[2], A[1],), (A[1],)
+
+    popdd = (A[3], A[2], A[1],), (A[2], A[1],)
+
+    swap = (A[1], A[2]), (A[2], A[1])
+
+    rest = ((A[1], S[1]),), (S[1],)
+
+    rrest = C(rest, rest)
+
+    cons = (A[1], S[1]), ((A[1], S[1]),)
+
+    ccons = C(cons, cons)
+
+    uncons = ((A[1], S[1]),), (A[1], S[1])
+
+    swons = C(swap, cons)
+
+    dup = (A[1],), (A[1], A[1])
+
+    dupd = (A[2], A[1]), (A[2], A[2], A[1])
+
+    mul = (N[1], N[2]), (N[3],)
+
+    sqrt = C(dup, mul)
+
+    first = ((A[1], S[1]),), (A[1],)
+
+    second = C(rest, first)
+
+    third = C(rest, second)
+
+    tuck = (A[2], A[1]), (A[1], A[2], A[1])
+
+    over = (A[2], A[1]), (A[2], A[1], A[2])
+
+    succ = pred = (N[1],), (N[2],)
+
+    divmod_ = pm = (N[2], N[1]), (N[4], N[3])
+
+    return locals()
+
+
+
DEFS = defs()
+
+
+
for name, stack_effect_comment in sorted(DEFS.items()):
+    print name, '=', doc_from_stack_effect(*stack_effect_comment)
+
+
+
ccons = (a0 a1 [.0.] -- [a0 a1 .0.])
+cons = (a1 [.1.] -- [a1 .1.])
+divmod_ = (n2 n1 -- n4 n3)
+dup = (a1 -- a1 a1)
+dupd = (a2 a1 -- a2 a2 a1)
+first = ([a1 .1.] -- a1)
+mul = (n1 n2 -- n3)
+over = (a2 a1 -- a2 a1 a2)
+pm = (n2 n1 -- n4 n3)
+pop = (a1 --)
+popd = (a2 a1 -- a1)
+popdd = (a3 a2 a1 -- a2 a1)
+popop = (a2 a1 --)
+pred = (n1 -- n2)
+rest = ([a1 .1.] -- [.1.])
+rolldown = (a1 a2 a3 -- a2 a3 a1)
+rollup = (a1 a2 a3 -- a3 a1 a2)
+rrest = ([a0 a1 .0.] -- [.0.])
+second = ([a0 a1 .0.] -- a1)
+sqrt = (n0 -- n1)
+succ = (n1 -- n2)
+swap = (a1 a2 -- a2 a1)
+swons = ([.0.] a0 -- [a0 .0.])
+third = ([a0 a1 a2 .0.] -- a2)
+tuck = (a2 a1 -- a1 a2 a1)
+uncons = ([a1 .1.] -- a1 [.1.])
+
+
+
globals().update(DEFS)
+
+
+
+
+

Compose dup and mul

+
C(dup, mul)
+
+
+
((n0,), (n1,))
+
+
+

Revisit the F function, works fine.

+
F = reduce(C, (pop, swap, rolldown, rest, rest, cons, cons))
+F
+
+
+
(((a0, (a1, s0)), a2, a3, a4), ((a3, (a2, s0)),))
+
+
+
print doc_from_stack_effect(*F)
+
+
+
([a0 a1 .0.] a2 a3 a4 -- [a3 a2 .0.])
+
+
+

Some otherwise inefficient functions are no longer to be feared. We can +also get the effect of combinators in some limited cases.

+
def neato(*funcs):
+    print doc_from_stack_effect(*reduce(C, funcs))
+
+
+
# e.g. [swap] dip
+neato(rollup, swap, rolldown)
+
+
+
(a0 a1 a2 -- a1 a0 a2)
+
+
+
# e.g. [popop] dipd
+neato(popdd, rolldown, pop)
+
+
+
(a0 a1 a2 a3 -- a2 a3)
+
+
+
# Reverse the order of the top three items.
+neato(rollup, swap)
+
+
+
(a0 a1 a2 -- a2 a1 a0)
+
+
+
+
+

compile_() version 2

+

Because the type labels represent themselves as valid Python identifiers +the compile_() function doesn’t need to generate them anymore:

+
def compile_(name, f, doc=None):
+    inputs, outputs = f
+    if doc is None:
+        doc = doc_from_stack_effect(inputs, outputs)
+    i = o = Symbol('stack')
+    for term in inputs:
+        i = term, i
+    for term in outputs:
+        o = term, o
+    return '''def %s(stack):
+    """%s"""
+    %s = stack
+    return %s''' % (name, doc, i, o)
+
+
+
print compile_('F', F)
+
+
+
def F(stack):
+    """([a0 a1 .0.] a2 a3 a4 -- [a3 a2 .0.])"""
+    (a4, (a3, (a2, ((a0, (a1, s0)), stack)))) = stack
+    return ((a3, (a2, s0)), stack)
+
+
+

But it cannot magically create new functions that involve e.g. math and +such. Note that this is not a sqr function implementation:

+
print compile_('sqr', C(dup, mul))
+
+
+
def sqr(stack):
+    """(n0 -- n1)"""
+    (n0, stack) = stack
+    return (n1, stack)
+
+
+

(Eventually I should come back around to this becuase it’s not tooo +difficult to exend this code to be able to compile e.g. +n3 = mul(n1, n2) for mul and insert it in the right place with +the right variable names. It requires a little more support from the +library functions, in that we need to know to call mul() the Python +function for mul the Joy function, but since most of the math +functions (at least) are already wrappers it should be straightforward.)

+
+
+

compilable()

+

The functions that can be compiled are the ones that have only +AnyJoyType and StackJoyType labels in their stack effect +comments. We can write a function to check that:

+
from itertools import imap
+
+
+def compilable(f):
+    return isinstance(f, tuple) and all(imap(compilable, f)) or stacky(f)
+
+
+
for name, stack_effect_comment in sorted(defs().items()):
+    if compilable(stack_effect_comment):
+        print name, '=', doc_from_stack_effect(*stack_effect_comment)
+
+
+
ccons = (a0 a1 [.0.] -- [a0 a1 .0.])
+cons = (a1 [.1.] -- [a1 .1.])
+dup = (a1 -- a1 a1)
+dupd = (a2 a1 -- a2 a2 a1)
+first = ([a1 .1.] -- a1)
+over = (a2 a1 -- a2 a1 a2)
+pop = (a1 --)
+popd = (a2 a1 -- a1)
+popdd = (a3 a2 a1 -- a2 a1)
+popop = (a2 a1 --)
+rest = ([a1 .1.] -- [.1.])
+rolldown = (a1 a2 a3 -- a2 a3 a1)
+rollup = (a1 a2 a3 -- a3 a1 a2)
+rrest = ([a0 a1 .0.] -- [.0.])
+second = ([a0 a1 .0.] -- a1)
+swap = (a1 a2 -- a2 a1)
+swons = ([.0.] a0 -- [a0 .0.])
+third = ([a0 a1 a2 .0.] -- a2)
+tuck = (a2 a1 -- a1 a2 a1)
+uncons = ([a1 .1.] -- a1 [.1.])
+
+
+
+
+
+
+

Part V: Functions that use the Stack

+

Consider the stack function which grabs the whole stack, quotes it, +and puts it on itself:

+
stack (...     -- ... [...]        )
+stack (... a   -- ... a [a ...]    )
+stack (... b a -- ... b a [a b ...])
+
+
+

We would like to represent this in Python somehow. To do this we use a +simple, elegant trick.

+
stack         S   -- (         S,           S)
+stack     (a, S)  -- (     (a, S),      (a, S))
+stack (a, (b, S)) -- ( (a, (b, S)), (a, (b, S)))
+
+
+

Instead of representing the stack effect comments as a single tuple +(with N items in it) we use the same cons-list structure to hold the +sequence and unify() the whole comments.

+
+

stack∘uncons

+

Let’s try composing stack and uncons. We want this result:

+
stack∘uncons (... a -- ... a a [...])
+
+
+

The stack effects are:

+
stack = S -- (S, S)
+
+uncons = ((a, Z), S) -- (Z, (a, S))
+
+
+

Unifying:

+
  S    -- (S, S) ∘ ((a, Z), S) -- (Z, (a,   S   ))
+                                                    w/ { S: (a, Z) }
+(a, Z) --        ∘             -- (Z, (a, (a, Z)))
+
+
+

So:

+
stack∘uncons == (a, Z) -- (Z, (a, (a, Z)))
+
+
+

It works.

+
+
+

stack∘uncons∘uncons

+

Let’s try stack∘uncons∘uncons:

+
(a, S     ) -- (S,      (a, (a, S     ))) ∘ ((b, Z),  S`             ) -- (Z, (b,   S`   ))
+
+                                                                                w/ { S: (b, Z) }
+
+(a, (b, Z)) -- ((b, Z), (a, (a, (b, Z)))) ∘ ((b, Z),  S`             ) -- (Z, (b,   S`   ))
+
+                                                                                w/ { S`: (a, (a, (b, Z))) }
+
+(a, (b, Z)) -- ((b, Z), (a, (a, (b, Z)))) ∘ ((b, Z), (a, (a, (b, Z)))) -- (Z, (b, (a, (a, (b, Z)))))
+
+(a, (b, Z)) -- (Z, (b, (a, (a, (b, Z)))))
+
+
+

It works.

+
+

compose() version 2

+

This function has to be modified to use the new datastructures and it is +no longer recursive, instead recursion happens as part of unification. +Further, the first and second of Pöial’s rules are now handled +automatically by the unification algorithm.

+
def compose(f, g):
+    (f_in, f_out), (g_in, g_out) = f, g
+    s = unify(g_in, f_out)
+    if s == False:  # s can also be the empty dict, which is ok.
+        raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
+    return update(s, (f_in, g_out))
+
+
+

I don’t want to rewrite all the defs myself, so I’ll write a little +conversion function instead. This is programmer’s laziness.

+
def sequence_to_stack(seq, stack=StackJoyType(23)):
+    for item in seq: stack = item, stack
+    return stack
+
+NEW_DEFS = {
+    name: (sequence_to_stack(i), sequence_to_stack(o))
+    for name, (i, o) in DEFS.iteritems()
+}
+NEW_DEFS['stack'] = S[0], (S[0], S[0])
+NEW_DEFS['swaack'] = (S[1], S[0]), (S[0], S[1])
+globals().update(NEW_DEFS)
+
+
+
C(stack, uncons)
+
+
+
((a0, s0), (s0, (a0, (a0, s0))))
+
+
+
C(C(stack, uncons), uncons)
+
+
+
((a0, (a1, s0)), (s0, (a1, (a0, (a0, (a1, s0))))))
+
+
+

The display function should be changed too.

+
+
+
+

doc_from_stack_effect() version 2

+

Clunky junk, but it will suffice for now.

+
def doc_from_stack_effect(inputs, outputs):
+    switch = [False]  # Do we need to display the '...' for the rest of the main stack?
+    i, o = _f(inputs, switch), _f(outputs, switch)
+    if switch[0]:
+        i.append('...')
+        o.append('...')
+    return '(%s--%s)' % (
+        ' '.join(reversed([''] + i)),
+        ' '.join(reversed(o + [''])),
+    )
+
+
+def _f(term, switch):
+    a = []
+    while term and isinstance(term, tuple):
+        item, term = term
+        a.append(item)
+    assert isinstance(term, StackJoyType), repr(term)
+    a = [_to_str(i, term, switch) for i in a]
+    return a
+
+
+def _to_str(term, stack, switch):
+    if not isinstance(term, tuple):
+        if term == stack:
+            switch[0] = True
+            return '[...]'
+        return (
+            '[.%i.]' % term.number
+            if isinstance(term, StackJoyType)
+            else str(term)
+        )
+
+    a = []
+    while term and isinstance(term, tuple):
+        item, term = term
+        a.append(_to_str(item, stack, switch))
+    assert isinstance(term, StackJoyType), repr(term)
+    if term == stack:
+        switch[0] = True
+        end = '...'
+    else:
+        end = '.%i.' % term.number
+    a.append(end)
+    return '[%s]' % ' '.join(a)
+
+
+
for name, stack_effect_comment in sorted(NEW_DEFS.items()):
+    print name, '=', doc_from_stack_effect(*stack_effect_comment)
+
+
+
ccons = (a0 a1 [.0.] -- [a0 a1 .0.])
+cons = (a1 [.1.] -- [a1 .1.])
+divmod_ = (n2 n1 -- n4 n3)
+dup = (a1 -- a1 a1)
+dupd = (a2 a1 -- a2 a2 a1)
+first = ([a1 .1.] -- a1)
+mul = (n1 n2 -- n3)
+over = (a2 a1 -- a2 a1 a2)
+pm = (n2 n1 -- n4 n3)
+pop = (a1 --)
+popd = (a2 a1 -- a1)
+popdd = (a3 a2 a1 -- a2 a1)
+popop = (a2 a1 --)
+pred = (n1 -- n2)
+rest = ([a1 .1.] -- [.1.])
+rolldown = (a1 a2 a3 -- a2 a3 a1)
+rollup = (a1 a2 a3 -- a3 a1 a2)
+rrest = ([a0 a1 .0.] -- [.0.])
+second = ([a0 a1 .0.] -- a1)
+sqrt = (n0 -- n1)
+stack = (... -- ... [...])
+succ = (n1 -- n2)
+swaack = ([.1.] -- [.0.])
+swap = (a1 a2 -- a2 a1)
+swons = ([.0.] a0 -- [a0 .0.])
+third = ([a0 a1 a2 .0.] -- a2)
+tuck = (a2 a1 -- a1 a2 a1)
+uncons = ([a1 .1.] -- a1 [.1.])
+
+
+
print ; print doc_from_stack_effect(*stack)
+print ; print doc_from_stack_effect(*C(stack, uncons))
+print ; print doc_from_stack_effect(*C(C(stack, uncons), uncons))
+print ; print doc_from_stack_effect(*C(C(stack, uncons), cons))
+
+
+
(... -- ... [...])
+
+(... a0 -- ... a0 a0 [...])
+
+(... a1 a0 -- ... a1 a0 a0 a1 [...])
+
+(... a0 -- ... a0 [a0 ...])
+
+
+
print doc_from_stack_effect(*C(ccons, stack))
+
+
+
(... a1 a0 [.0.] -- ... [a1 a0 .0.] [[a1 a0 .0.] ...])
+
+
+
Q = C(ccons, stack)
+
+Q
+
+
+
((s0, (a0, (a1, s1))), (((a1, (a0, s0)), s1), ((a1, (a0, s0)), s1)))
+
+
+
+

compile_() version 3

+

This makes the compile_() function pretty simple as the stack effect +comments are now already in the form needed for the Python code:

+
def compile_(name, f, doc=None):
+    i, o = f
+    if doc is None:
+        doc = doc_from_stack_effect(i, o)
+    return '''def %s(stack):
+    """%s"""
+    %s = stack
+    return %s''' % (name, doc, i, o)
+
+
+
print compile_('Q', Q)
+
+
+
def Q(stack):
+    """(... a1 a0 [.0.] -- ... [a1 a0 .0.] [[a1 a0 .0.] ...])"""
+    (s0, (a0, (a1, s1))) = stack
+    return (((a1, (a0, s0)), s1), ((a1, (a0, s0)), s1))
+
+
+
unstack = (S[1], S[0]), S[1]
+enstacken = S[0], (S[0], S[1])
+
+
+
print doc_from_stack_effect(*unstack)
+
+
+
([.1.] --)
+
+
+
print doc_from_stack_effect(*enstacken)
+
+
+
(-- [.0.])
+
+
+
print doc_from_stack_effect(*C(cons, unstack))
+
+
+
(a0 [.0.] -- a0)
+
+
+
print doc_from_stack_effect(*C(cons, enstacken))
+
+
+
(a0 [.0.] -- [[a0 .0.] .1.])
+
+
+
C(cons, unstack)
+
+
+
((s0, (a0, s1)), (a0, s0))
+
+
+
+
+
+
+

Part VI: Multiple Stack Effects

+

+
class IntJoyType(NumberJoyType): prefix = 'i'
+
+
+F = map(FloatJoyType, _R)
+I = map(IntJoyType, _R)
+
+
+
muls = [
+     ((I[2], (I[1], S[0])), (I[3], S[0])),
+     ((F[2], (I[1], S[0])), (F[3], S[0])),
+     ((I[2], (F[1], S[0])), (F[3], S[0])),
+     ((F[2], (F[1], S[0])), (F[3], S[0])),
+]
+
+
+
for f in muls:
+    print doc_from_stack_effect(*f)
+
+
+
(i1 i2 -- i3)
+(i1 f2 -- f3)
+(f1 i2 -- f3)
+(f1 f2 -- f3)
+
+
+
for f in muls:
+    try:
+        e = C(dup, f)
+    except TypeError:
+        continue
+    print doc_from_stack_effect(*dup), doc_from_stack_effect(*f), doc_from_stack_effect(*e)
+
+
+
(a1 -- a1 a1) (i1 i2 -- i3) (i0 -- i1)
+(a1 -- a1 a1) (f1 f2 -- f3) (f0 -- f1)
+
+
+
from itertools import product
+
+
+def meta_compose(F, G):
+    for f, g in product(F, G):
+        try:
+            yield C(f, g)
+        except TypeError:
+            pass
+
+
+def MC(F, G):
+    return sorted(set(meta_compose(F, G)))
+
+
+
for f in MC([dup], muls):
+    print doc_from_stack_effect(*f)
+
+
+
(f0 -- f1)
+(i0 -- i1)
+
+
+
for f in MC([dup], [mul]):
+    print doc_from_stack_effect(*f)
+
+
+
(n0 -- n1)
+
+
+
+

Representing an Unbounded Sequence of Types

+

We can borrow a trick from Brzozowski’s Derivatives of Regular +Expressions to +invent a new type of type variable, a “sequence type” (I think this is +what they mean in the literature by that term…) or “Kleene +Star” type. I’m going to +represent it as a type letter and the asterix, so a sequence of zero or +more AnyJoyType variables would be:

+
A*
+
+
+

The A* works by splitting the universe into two alternate histories:

+
A* -> 0 | A A*
+
+
+

The Kleene star variable disappears in one universe, and in the other it +turns into an AnyJoyType variable followed by itself again. We have +to return all universes (represented by their substitution dicts, the +“unifiers”) that don’t lead to type conflicts.

+

Consider unifying two stacks (the lowercase letters are any type +variables of the kinds we have defined so far):

+
[a A* b .0.] U [c d .1.]
+                          w/ {c: a}
+[  A* b .0.] U [  d .1.]
+
+
+

Now we have to split universes to unify A*. In the first universe it +disappears:

+
[b .0.] U [d .1.]
+                   w/ {d: b, .1.: .0.}
+     [] U []
+
+
+

While in the second it spawns an A, which we will label e:

+
[e A* b .0.] U [d .1.]
+                        w/ {d: e}
+[  A* b .0.] U [  .1.]
+                        w/ {.1.: A* b .0.}
+[  A* b .0.] U [  A* b .0.]
+
+
+

Giving us two unifiers:

+
{c: a,  d: b,  .1.:      .0.}
+{c: a,  d: e,  .1.: A* b .0.}
+
+
+
class KleeneStar(object):
+
+    kind = AnyJoyType
+
+    def __init__(self, number):
+        self.number = number
+        self.count = 0
+        self.prefix = repr(self)
+
+    def __repr__(self):
+        return '%s%i*' % (self.kind.prefix, self.number)
+
+    def another(self):
+        self.count += 1
+        return self.kind(10000 * self.number + self.count)
+
+    def __eq__(self, other):
+        return (
+            isinstance(other, self.__class__)
+            and other.number == self.number
+        )
+
+    def __ge__(self, other):
+        return self.kind >= other.kind
+
+    def __add__(self, other):
+        return self.__class__(self.number + other)
+    __radd__ = __add__
+
+    def __hash__(self):
+        return hash(repr(self))
+
+class AnyStarJoyType(KleeneStar): kind = AnyJoyType
+class NumberStarJoyType(KleeneStar): kind = NumberJoyType
+#class FloatStarJoyType(KleeneStar): kind = FloatJoyType
+#class IntStarJoyType(KleeneStar): kind = IntJoyType
+class StackStarJoyType(KleeneStar): kind = StackJoyType
+
+
+As = map(AnyStarJoyType, _R)
+Ns = map(NumberStarJoyType, _R)
+Ss = map(StackStarJoyType, _R)
+
+
+
+

unify() version 4

+

Can now return multiple results…

+
def unify(u, v, s=None):
+    if s is None:
+        s = {}
+    elif s:
+        u = update(s, u)
+        v = update(s, v)
+
+    if u == v:
+        return s,
+
+    if isinstance(u, AnyJoyType) and isinstance(v, AnyJoyType):
+        if u >= v:
+            s[u] = v
+            return s,
+        if v >= u:
+            s[v] = u
+            return s,
+        raise TypeError('Cannot unify %r and %r.' % (u, v))
+
+    if isinstance(u, tuple) and isinstance(v, tuple):
+        if len(u) != len(v) != 2:
+            raise TypeError(repr((u, v)))
+
+        a, b = v
+        if isinstance(a, KleeneStar):
+            # Two universes, in one the Kleene star disappears and unification
+            # continues without it...
+            s0 = unify(u, b)
+
+            # In the other it spawns a new variable.
+            s1 = unify(u, (a.another(), v))
+
+            t = s0 + s1
+            for sn in t:
+                sn.update(s)
+            return t
+
+        a, b = u
+        if isinstance(a, KleeneStar):
+            s0 = unify(v, b)
+            s1 = unify(v, (a.another(), u))
+            t = s0 + s1
+            for sn in t:
+                sn.update(s)
+            return t
+
+        ses = unify(u[0], v[0], s)
+        results = ()
+        for sn in ses:
+            results += unify(u[1], v[1], sn)
+        return results
+
+    if isinstance(v, tuple):
+        if not stacky(u):
+            raise TypeError('Cannot unify %r and %r.' % (u, v))
+        s[u] = v
+        return s,
+
+    if isinstance(u, tuple):
+        if not stacky(v):
+            raise TypeError('Cannot unify %r and %r.' % (v, u))
+        s[v] = u
+        return s,
+
+    return ()
+
+
+def stacky(thing):
+    return thing.__class__ in {AnyJoyType, StackJoyType}
+
+
+
a = (As[1], S[1])
+a
+
+
+
(a1*, s1)
+
+
+
b = (A[1], S[2])
+b
+
+
+
(a1, s2)
+
+
+
for result in unify(b, a):
+    print result, '->', update(result, a), update(result, b)
+
+
+
{s1: (a1, s2)} -> (a1*, (a1, s2)) (a1, s2)
+{a1: a10001, s2: (a1*, s1)} -> (a1*, s1) (a10001, (a1*, s1))
+
+
+
for result in unify(a, b):
+    print result, '->', update(result, a), update(result, b)
+
+
+
{s1: (a1, s2)} -> (a1*, (a1, s2)) (a1, s2)
+{a1: a10002, s2: (a1*, s1)} -> (a1*, s1) (a10002, (a1*, s1))
+
+
+
(a1*, s1)       [a1*]       (a1, s2)        [a1]
+
+(a1*, (a1, s2)) [a1* a1]    (a1, s2)        [a1]
+
+(a1*, s1)       [a1*]       (a2, (a1*, s1)) [a2 a1*]
+
+
+
sum_ = ((Ns[1], S[1]), S[0]), (N[0], S[0])
+
+print doc_from_stack_effect(*sum_)
+
+
+
([n1* .1.] -- n0)
+
+
+
f = (N[1], (N[2], (N[3], S[1]))), S[0]
+
+print doc_from_stack_effect(S[0], f)
+
+
+
(-- [n1 n2 n3 .1.])
+
+
+
for result in unify(sum_[0], f):
+    print result, '->', update(result, sum_[1])
+
+
+
{s1: (n1, (n2, (n3, s1)))} -> (n0, s0)
+{n1: n10001, s1: (n2, (n3, s1))} -> (n0, s0)
+{n1: n10001, s1: (n3, s1), n2: n10002} -> (n0, s0)
+{n1: n10001, s1: (n1*, s1), n3: n10003, n2: n10002} -> (n0, s0)
+
+
+
+
+

compose() version 3

+

This function has to be modified to yield multiple results.

+
def compose(f, g):
+    (f_in, f_out), (g_in, g_out) = f, g
+    s = unify(g_in, f_out)
+    if not s:
+        raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
+    for result in s:
+        yield update(result, (f_in, g_out))
+
+
+
def meta_compose(F, G):
+    for f, g in product(F, G):
+        try:
+            for result in C(f, g):
+                yield result
+        except TypeError:
+            pass
+
+
+def C(f, g):
+    f, g = relabel(f, g)
+    for fg in compose(f, g):
+        yield delabel(fg)
+
+
+
for f in MC([dup], muls):
+    print doc_from_stack_effect(*f)
+
+
+
(f0 -- f1)
+(i0 -- i1)
+
+
+
for f in MC([dup], [sum_]):
+    print doc_from_stack_effect(*f)
+
+
+
([n0* .0.] -- [n0* .0.] n0)
+
+
+
for f in MC([cons], [sum_]):
+    print doc_from_stack_effect(*f)
+
+
+
(a0 [.0.] -- n0)
+(n0 [n0* .0.] -- n1)
+
+
+
sum_ = (((N[1], (Ns[1], S[1])), S[0]), (N[0], S[0]))
+print doc_from_stack_effect(*cons),
+print doc_from_stack_effect(*sum_),
+
+for f in MC([cons], [sum_]):
+    print doc_from_stack_effect(*f)
+
+
+
(a1 [.1.] -- [a1 .1.]) ([n1 n1* .1.] -- n0) (n0 [n0* .0.] -- n1)
+
+
+
a = (A[4], (As[1], (A[3], S[1])))
+a
+
+
+
(a4, (a1*, (a3, s1)))
+
+
+
b = (A[1], (A[2], S[2]))
+b
+
+
+
(a1, (a2, s2))
+
+
+
for result in unify(b, a):
+    print result
+
+
+
{a1: a4, s2: s1, a2: a3}
+{a1: a4, s2: (a1*, (a3, s1)), a2: a10003}
+
+
+
for result in unify(a, b):
+    print result
+
+
+
{s2: s1, a2: a3, a4: a1}
+{s2: (a1*, (a3, s1)), a2: a10004, a4: a1}
+
+
+
+
+
+
+

Part VII: Typing Combinators

+

In order to compute the stack effect of combinators you kinda have to +have the quoted programs they expect available. In the most general +case, the i combinator, you can’t say anything about it’s stack +effect other than it expects one quote:

+
i (... [.1.] -- ... .1.)
+
+
+

Or

+
i (... [A* .1.] -- ... A*)
+
+
+

Consider the type of:

+
[cons] dip
+
+
+

Obviously it would be:

+
(a1 [..1] a2 -- [a1 ..1] a2)
+
+
+

dip itself could have:

+
(a1 [..1] -- ... then what?
+
+
+

Without any information about the contents of the quote we can’t say +much about the result.

+

I think there’s a way forward. If we convert our list of terms we are +composing into a stack structure we can use it as a Joy expression, +then we can treat the output half of a function’s stack effect comment +as a Joy interpreter stack, and just execute combinators directly. We +can hybridize the compostition function with an interpreter to evaluate +combinators, compose non-combinator functions, and put type variables on +the stack. For combinators like branch that can have more than one +stack effect we have to “split universes” again and return both.

+
class FunctionJoyType(AnyJoyType):
+
+    def __init__(self, name, sec, number):
+        self.name = name
+        self.stack_effects = sec
+        self.number = number
+
+    def __add__(self, other):
+        return self
+    __radd__ = __add__
+
+    def __repr__(self):
+        return self.name
+
+
+class SymbolJoyType(FunctionJoyType): prefix = 'F'
+class CombinatorJoyType(FunctionJoyType): prefix = 'C'
+
+
+
def flatten(g):
+    return list(chain.from_iterable(g))
+
+
+ID = S[0], S[0]  # Identity function.
+
+
+def infer(e, F=ID):
+    if not e:
+        return [F]
+
+    n, e = e
+
+    if isinstance(n, SymbolJoyType):
+        res = flatten(infer(e, Fn) for Fn in MC([F], n.stack_effects))
+
+    elif isinstance(n, CombinatorJoyType):
+        res = []
+        for combinator in n.stack_effects:
+            fi, fo = F
+            new_fo, ee, _ = combinator(fo, e, {})
+            ee = update(FUNCTIONS, ee)  # Fix Symbols.
+            new_F = fi, new_fo
+            res.extend(infer(ee, new_F))
+    else:
+        lit = s9, (n, s9)
+        res = flatten(infer(e, Fn) for Fn in MC([F], [lit]))
+
+    return res
+
+
+
f0, f1, f2, f3, f4, f5, f6, f7, f8, f9 = F = map(FloatJoyType, _R)
+i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 = I = map(IntJoyType, _R)
+n0, n1, n2, n3, n4, n5, n6, n7, n8, n9 = N
+s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 = S
+
+
+
import joy.library
+
+FNs = '''ccons cons divmod_ dup dupd first
+         over pm pop popd popdd popop pred
+         rest rolldown rollup rrest second
+         sqrt stack succ swaack swap swons
+         third tuck uncons'''
+
+FUNCTIONS = {
+    name: SymbolJoyType(name, [NEW_DEFS[name]], i)
+    for i, name in enumerate(FNs.strip().split())
+    }
+FUNCTIONS['sum'] = SymbolJoyType('sum', [(((Ns[1], s1), s0), (n0, s0))], 100)
+FUNCTIONS['mul'] = SymbolJoyType('mul', [
+     ((i2, (i1, s0)), (i3, s0)),
+     ((f2, (i1, s0)), (f3, s0)),
+     ((i2, (f1, s0)), (f3, s0)),
+     ((f2, (f1, s0)), (f3, s0)),
+], 101)
+FUNCTIONS.update({
+    combo.__name__: CombinatorJoyType(combo.__name__, [combo], i)
+    for i, combo in enumerate((
+        joy.library.i,
+        joy.library.dip,
+        joy.library.dipd,
+        joy.library.dipdd,
+        joy.library.dupdip,
+        joy.library.b,
+        joy.library.x,
+        joy.library.infra,
+        ))
+    })
+
+def branch_true(stack, expression, dictionary):
+    (then, (else_, (flag, stack))) = stack
+    return stack, CONCAT(then, expression), dictionary
+
+def branch_false(stack, expression, dictionary):
+    (then, (else_, (flag, stack))) = stack
+    return stack, CONCAT(else_, expression), dictionary
+
+FUNCTIONS['branch'] = CombinatorJoyType('branch', [branch_true, branch_false], 100)
+
+
+
globals().update(FUNCTIONS)
+
+
+
from itertools import chain
+from joy.utils.stack import list_to_stack as l2s
+
+
+
expression = l2s([n1, n2, (mul, s2), (stack, s3), dip, infra, first])
+
+
+
expression
+
+
+
(n1, (n2, ((mul, s2), ((stack, s3), (dip, (infra, (first, ())))))))
+
+
+
expression = l2s([n1, n2, mul])
+
+
+
infer(expression)
+
+
+
[]
+
+
+
class SymbolJoyType(AnyJoyType):
+    prefix = 'F'
+
+    def __init__(self, name, sec, number):
+        self.name = name
+        self.stack_effects = sec
+        self.number = number
+
+class CombinatorJoyType(SymbolJoyType): prefix = 'C'
+
+def dip_t(stack, expression):
+    (quote, (a1, stack)) = stack
+    expression = stack_concat(quote, (a1, expression))
+    return stack, expression
+
+CONS = SymbolJoyType('cons', [cons], 23)
+DIP = CombinatorJoyType('dip', [dip_t], 44)
+
+
+def kav(F, e):
+    #i, stack = F
+    if not e:
+        return [(F, e)]
+    n, e = e
+    if isinstance(n, SymbolJoyType):
+        Fs = []
+        for sec in n.stack_effects:
+            Fs.extend(MC([F], sec))
+        return [kav(Fn, e) for Fn in Fs]
+    if isinstance(n, CombinatorJoyType):
+        res = []
+        for f in n.stack_effects:
+            s, e = f(F[1], e)
+            new_F = F[0], s
+            res.extend(kav(new_F, e))
+        return res
+    lit = S[0], (n, S[0])
+    return [kav(Fn, e) for Fn in MC([F], [lit])]
+
+
+

compare, and be amazed:

+
def dip_t(stack, expression):
+    (quote, (a1, stack)) = stack
+    expression = stack_concat(quote, (a1, expression))
+    return stack, expression
+
+
+
def dip(stack, expression, dictionary):
+    (quote, (x, stack)) = stack
+    expression = (x, expression)
+    return stack, concat(quote, expression), dictionary
+
+
+

And that brings us to current Work-In-Progress. I’m pretty hopeful that +the mixed-mode inferencer/interpreter kav() function along with the +ability to specify multiple implementations for the combinators will +permit modelling of the stack effects of e.g. ifte. If I can keep up +the pace I should be able to verify that conjecture by the end of June.

+

The rest of this stuff is junk and/or unfinished material.

+
+
+

Appendix: Joy in the Logical Paradigm

+

For this to work the type label classes have to be modified to let +T >= t succeed, where e.g. T is IntJoyType and t is +int

+
F = reduce(C, (pop, swap, rolldown, rest, rest, cons, cons))
+
+print doc_from_stack_effect(*F)
+
+
+
---------------------------------------------------------------------------
+
+TypeError                                 Traceback (most recent call last)
+
+<ipython-input-119-7fde90b4e88f> in <module>()
+      1 F = reduce(C, (pop, swap, rolldown, rest, rest, cons, cons))
+      2
+----> 3 print doc_from_stack_effect(*F)
+
+
+<ipython-input-98-ddee30dbb1a6> in C(f, g)
+     10 def C(f, g):
+     11     f, g = relabel(f, g)
+---> 12     for fg in compose(f, g):
+     13         yield delabel(fg)
+
+
+<ipython-input-97-5eb7ac5ad2c2> in compose(f, g)
+      1 def compose(f, g):
+----> 2     (f_in, f_out), (g_in, g_out) = f, g
+      3     s = unify(g_in, f_out)
+      4     if not s:
+      5         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
+
+
+<ipython-input-98-ddee30dbb1a6> in C(f, g)
+     10 def C(f, g):
+     11     f, g = relabel(f, g)
+---> 12     for fg in compose(f, g):
+     13         yield delabel(fg)
+
+
+<ipython-input-97-5eb7ac5ad2c2> in compose(f, g)
+      1 def compose(f, g):
+----> 2     (f_in, f_out), (g_in, g_out) = f, g
+      3     s = unify(g_in, f_out)
+      4     if not s:
+      5         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
+
+
+<ipython-input-98-ddee30dbb1a6> in C(f, g)
+     10 def C(f, g):
+     11     f, g = relabel(f, g)
+---> 12     for fg in compose(f, g):
+     13         yield delabel(fg)
+
+
+<ipython-input-97-5eb7ac5ad2c2> in compose(f, g)
+      1 def compose(f, g):
+----> 2     (f_in, f_out), (g_in, g_out) = f, g
+      3     s = unify(g_in, f_out)
+      4     if not s:
+      5         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
+
+
+<ipython-input-98-ddee30dbb1a6> in C(f, g)
+     10 def C(f, g):
+     11     f, g = relabel(f, g)
+---> 12     for fg in compose(f, g):
+     13         yield delabel(fg)
+
+
+<ipython-input-97-5eb7ac5ad2c2> in compose(f, g)
+      1 def compose(f, g):
+----> 2     (f_in, f_out), (g_in, g_out) = f, g
+      3     s = unify(g_in, f_out)
+      4     if not s:
+      5         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
+
+
+<ipython-input-98-ddee30dbb1a6> in C(f, g)
+     10 def C(f, g):
+     11     f, g = relabel(f, g)
+---> 12     for fg in compose(f, g):
+     13         yield delabel(fg)
+
+
+<ipython-input-97-5eb7ac5ad2c2> in compose(f, g)
+      1 def compose(f, g):
+----> 2     (f_in, f_out), (g_in, g_out) = f, g
+      3     s = unify(g_in, f_out)
+      4     if not s:
+      5         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
+
+
+<ipython-input-98-ddee30dbb1a6> in C(f, g)
+     10 def C(f, g):
+     11     f, g = relabel(f, g)
+---> 12     for fg in compose(f, g):
+     13         yield delabel(fg)
+
+
+<ipython-input-97-5eb7ac5ad2c2> in compose(f, g)
+      1 def compose(f, g):
+----> 2     (f_in, f_out), (g_in, g_out) = f, g
+      3     s = unify(g_in, f_out)
+      4     if not s:
+      5         raise TypeError('Cannot unify %r and %r.' % (f_out, g_in))
+
+
+TypeError: 'SymbolJoyType' object is not iterable
+
+
+
from joy.parser import text_to_expression
+
+
+
s = text_to_expression('[3 4 ...] 2 1')
+s
+
+
+
L = unify(F[1], s)
+L
+
+
+
F[1]
+
+
+
F[1][0]
+
+
+
s[0]
+
+
+
+
+

Abstract Interpretation

+

I think this might be sorta what I’m doing above with the kav() +function… In any event “mixed-mode” interpreters that include values +and type variables and can track constraints, etc. will be, uh, +super-useful. And Abstract Interpretation should be a rich source of +ideas.

+
+
+

Junk

+
class SymbolJoyType(AnyJoyType): prefix = 'F'
+
+W = map(SymbolJoyType, _R)
+
+k = S[0], ((W[1], S[2]), S[0])
+Symbol('cons')
+print doc_from_stack_effect(*k)
+
+
+
dip_a = ((W[1], S[2]), (A[1], S[0]))
+
+
+
d = relabel(S[0], dip_a)
+print doc_from_stack_effect(*d)
+
+
+
s = list(unify(d[1], k[1]))[0]
+s
+
+
+
j = update(s, k)
+
+
+
print doc_from_stack_effect(*j)
+
+
+
j
+
+
+
cons
+
+
+
for f in MC([k], [dup]):
+    print doc_from_stack_effect(*f)
+
+
+
l = S[0], ((cons, S[2]), (A[1], S[0]))
+
+
+
print doc_from_stack_effect(*l)
+
+
+
def dip_t(F):
+    (quote, (a1, sec)) = F[1]
+    G = F[0], sec
+    P = S[3], (a1, S[3])
+    a = [P]
+    while isinstance(quote, tuple):
+        term, quote = quote
+        a.append(term)
+    a.append(G)
+    return a[::-1]
+
+
+
from joy.utils.stack import iter_stack
+
+
+
a, b, c = dip_t(l)
+
+
+
a
+
+
+
b
+
+
+
c
+
+
+
MC([a], [b])
+
+
+
kjs = MC(MC([a], [b]), [c])
+kjs
+
+
+
print doc_from_stack_effect(*kjs[0])
+
+
+
(a0 [.0.] -- [a0 .0.] a1)
+
+   a0 [.0.] a1 [cons] dip
+----------------------------
+   [a0 .0.] a1
+
+
+
+

concat

+

How to deal with concat?

+
concat ([.0.] [.1.] -- [.0. .1.])
+
+
+

We would like to represent this in Python somehow…

+
concat = (S[0], S[1]), ((S[0], S[1]),)
+
+
+

But this is actually cons with the first argument restricted to be a +stack:

+
([.0.] [.1.] -- [[.0.] .1.])
+
+
+

What we have implemented so far would actually only permit:

+
([.0.] [.1.] -- [.2.])
+
+
+
concat = (S[0], S[1]), (S[2],)
+
+
+

Which works but can lose information. Consider cons concat, this is +how much information we could retain:

+
(1 [.0.] [.1.] -- [1 .0. .1.])
+
+
+

As opposed to just:

+
(1 [.0.] [.1.] -- [.2.])
+
+
+
+
+

represent concat

+
([.0.] [.1.] -- [A*(.0.) .1.])
+
+
+

Meaning that A* on the right-hand side should all the crap from +.0..

+
([      .0.] [.1.] -- [      A* .1.])
+([a     .0.] [.1.] -- [a     A* .1.])
+([a b   .0.] [.1.] -- [a b   A* .1.])
+([a b c .0.] [.1.] -- [a b c A* .1.])
+
+
+

or…

+
([       .0.] [.1.] -- [       .1.])
+([a      .0.] [.1.] -- [a      .1.])
+([a b    .0.] [.1.] -- [a b    .1.])
+([a b  c .0.] [.1.] -- [a b  c .1.])
+([a A* c .0.] [.1.] -- [a A* c .1.])
+
+
+
(a, (b, S0)) . S1 = (a, (b, (A*, S1)))
+
+
+
class Astar(object):
+    def __repr__(self):
+        return 'A*'
+
+
+def concat(s0, s1):
+    a = []
+    while isinstance(s0, tuple):
+        term, s0 = s0
+        a.append(term)
+    assert isinstance(s0, StackJoyType), repr(s0)
+    s1 = Astar(), s1
+    for term in reversed(a):
+        s1 = term, s1
+    return s1
+
+
+
a, b = (A[1], S[0]), (A[2], S[1])
+
+
+
concat(a, b)
+
+
+
+
+
+ + +
+
+
+ +
+
+ + + + \ No newline at end of file