Rebuild docs
This commit is contained in:
@@ -1,69 +1,69 @@
|
||||
∂RE
|
||||
===
|
||||
|
||||
Brzozowski's Derivatives of Regular Expressions
|
||||
Brzozowski’s Derivatives of Regular Expressions
|
||||
-----------------------------------------------
|
||||
|
||||
Legend:
|
||||
|
||||
::
|
||||
|
||||
∧ intersection
|
||||
∨ union
|
||||
∘ concatenation (see below)
|
||||
¬ complement
|
||||
ϕ empty set (aka ∅)
|
||||
λ singleton set containing just the empty string
|
||||
I set of all letters in alphabet
|
||||
∧ intersection
|
||||
∨ union
|
||||
∘ concatenation (see below)
|
||||
¬ complement
|
||||
ϕ empty set (aka ∅)
|
||||
λ singleton set containing just the empty string
|
||||
I set of all letters in alphabet
|
||||
|
||||
Derivative of a set ``R`` of strings and a string ``a``:
|
||||
|
||||
::
|
||||
|
||||
∂a(R)
|
||||
∂a(R)
|
||||
|
||||
∂a(a) → λ
|
||||
∂a(λ) → ϕ
|
||||
∂a(ϕ) → ϕ
|
||||
∂a(¬a) → ϕ
|
||||
∂a(R*) → ∂a(R)∘R*
|
||||
∂a(¬R) → ¬∂a(R)
|
||||
∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
|
||||
∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
|
||||
∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
||||
∂a(a) → λ
|
||||
∂a(λ) → ϕ
|
||||
∂a(ϕ) → ϕ
|
||||
∂a(¬a) → ϕ
|
||||
∂a(R*) → ∂a(R)∘R*
|
||||
∂a(¬R) → ¬∂a(R)
|
||||
∂a(R∘S) → ∂a(R)∘S ∨ δ(R)∘∂a(S)
|
||||
∂a(R ∧ S) → ∂a(R) ∧ ∂a(S)
|
||||
∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
||||
|
||||
∂ab(R) = ∂b(∂a(R))
|
||||
∂ab(R) = ∂b(∂a(R))
|
||||
|
||||
Auxiliary predicate function ``δ`` (I call it ``nully``) returns either
|
||||
``λ`` if ``λ ⊆ R`` or ``ϕ`` otherwise:
|
||||
|
||||
::
|
||||
|
||||
δ(a) → ϕ
|
||||
δ(λ) → λ
|
||||
δ(ϕ) → ϕ
|
||||
δ(R*) → λ
|
||||
δ(¬R) δ(R)≟ϕ → λ
|
||||
δ(¬R) δ(R)≟λ → ϕ
|
||||
δ(R∘S) → δ(R) ∧ δ(S)
|
||||
δ(R ∧ S) → δ(R) ∧ δ(S)
|
||||
δ(R ∨ S) → δ(R) ∨ δ(S)
|
||||
δ(a) → ϕ
|
||||
δ(λ) → λ
|
||||
δ(ϕ) → ϕ
|
||||
δ(R*) → λ
|
||||
δ(¬R) δ(R)≟ϕ → λ
|
||||
δ(¬R) δ(R)≟λ → ϕ
|
||||
δ(R∘S) → δ(R) ∧ δ(S)
|
||||
δ(R ∧ S) → δ(R) ∧ δ(S)
|
||||
δ(R ∨ S) → δ(R) ∨ δ(S)
|
||||
|
||||
Some rules we will use later for "compaction":
|
||||
Some rules we will use later for “compaction”:
|
||||
|
||||
::
|
||||
|
||||
R ∧ ϕ = ϕ ∧ R = ϕ
|
||||
R ∧ ϕ = ϕ ∧ R = ϕ
|
||||
|
||||
R ∧ I = I ∧ R = R
|
||||
R ∧ I = I ∧ R = R
|
||||
|
||||
R ∨ ϕ = ϕ ∨ R = R
|
||||
R ∨ ϕ = ϕ ∨ R = R
|
||||
|
||||
R ∨ I = I ∨ R = I
|
||||
R ∨ I = I ∨ R = I
|
||||
|
||||
R∘ϕ = ϕ∘R = ϕ
|
||||
R∘ϕ = ϕ∘R = ϕ
|
||||
|
||||
R∘λ = λ∘R = R
|
||||
R∘λ = λ∘R = R
|
||||
|
||||
Concatination of sets: for two sets A and B the set A∘B is defined as:
|
||||
|
||||
@@ -71,7 +71,7 @@ Concatination of sets: for two sets A and B the set A∘B is defined as:
|
||||
|
||||
E.g.:
|
||||
|
||||
{'a', 'b'}∘{'c', 'd'} → {'ac', 'ad', 'bc', 'bd'}
|
||||
{‘a’, ‘b’}∘{‘c’, ‘d’} → {‘ac’, ‘ad’, ‘bc’, ‘bd’}
|
||||
|
||||
Implementation
|
||||
--------------
|
||||
@@ -94,11 +94,11 @@ The empty set and the set of just the empty string.
|
||||
Two-letter Alphabet
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
I'm only going to use two symbols (at first) becaase this is enough to
|
||||
I’m only going to use two symbols (at first) becaase this is enough to
|
||||
illustrate the algorithm and because you can represent any other
|
||||
alphabet with two symbols (if you had to.)
|
||||
|
||||
I chose the names ``O`` and ``l`` (uppercase "o" and lowercase "L") to
|
||||
I chose the names ``O`` and ``l`` (uppercase “o” and lowercase “L”) to
|
||||
look like ``0`` and ``1`` (zero and one) respectively.
|
||||
|
||||
.. code:: ipython2
|
||||
@@ -108,18 +108,18 @@ look like ``0`` and ``1`` (zero and one) respectively.
|
||||
Representing Regular Expressions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To represent REs in Python I'm going to use tagged tuples. A *regular
|
||||
To represent REs in Python I’m going to use tagged tuples. A *regular
|
||||
expression* is one of:
|
||||
|
||||
::
|
||||
|
||||
O
|
||||
l
|
||||
(KSTAR, R)
|
||||
(NOT, R)
|
||||
(AND, R, S)
|
||||
(CONS, R, S)
|
||||
(OR, R, S)
|
||||
O
|
||||
l
|
||||
(KSTAR, R)
|
||||
(NOT, R)
|
||||
(AND, R, S)
|
||||
(CONS, R, S)
|
||||
(OR, R, S)
|
||||
|
||||
Where ``R`` and ``S`` stand for *regular expressions*.
|
||||
|
||||
@@ -169,11 +169,11 @@ String Representation of RE Datastructures
|
||||
``I``
|
||||
~~~~~
|
||||
|
||||
Match anything. Often spelled "."
|
||||
Match anything. Often spelled “.”
|
||||
|
||||
::
|
||||
|
||||
I = (0|1)*
|
||||
I = (0|1)*
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -196,8 +196,8 @@ The example expression from Brzozowski:
|
||||
|
||||
::
|
||||
|
||||
(.111.) & (.01 + 11*)'
|
||||
a & (b + c)'
|
||||
(.111.) & (.01 + 11*)'
|
||||
a & (b + c)'
|
||||
|
||||
Note that it contains one of everything.
|
||||
|
||||
@@ -221,7 +221,7 @@ Note that it contains one of everything.
|
||||
``nully()``
|
||||
~~~~~~~~~~~
|
||||
|
||||
Let's get that auxiliary predicate function ``δ`` out of the way.
|
||||
Let’s get that auxiliary predicate function ``δ`` out of the way.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -256,10 +256,10 @@ Let's get that auxiliary predicate function ``δ`` out of the way.
|
||||
r, s = nully(R[1]), nully(R[2])
|
||||
return r & s if tag in {AND, CONS} else r | s
|
||||
|
||||
No "Compaction"
|
||||
No “Compaction”
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This is the straightforward version with no "compaction". It works fine,
|
||||
This is the straightforward version with no “compaction”. It works fine,
|
||||
but does waaaay too much work because the expressions grow each
|
||||
derivation.
|
||||
|
||||
@@ -359,7 +359,7 @@ are *pure* so this is fine.
|
||||
result = self.mem[key] = self.f(key)
|
||||
return result
|
||||
|
||||
With "Compaction"
|
||||
With “Compaction”
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
This version uses the rules above to perform compaction. It keeps the
|
||||
@@ -409,8 +409,8 @@ expressions from growing too large.
|
||||
|
||||
return derv
|
||||
|
||||
Let's try it out...
|
||||
-------------------
|
||||
Let’s try it out…
|
||||
-----------------
|
||||
|
||||
(FIXME: redo.)
|
||||
|
||||
@@ -460,27 +460,27 @@ Should match:
|
||||
|
||||
::
|
||||
|
||||
(.111.) & ((.01 | 11*)')
|
||||
(.111.) & ((.01 | 11*)')
|
||||
|
||||
92 / 122
|
||||
92 / 122
|
||||
92 / 122
|
||||
92 / 122
|
||||
|
||||
(.01 )'
|
||||
(.01 | 1 )'
|
||||
(.01 | ^ )'
|
||||
(.01 | 1*)'
|
||||
(.111.) & ((.01 | 1 )')
|
||||
(.111. | 11.) & ((.01 | ^ )')
|
||||
(.111. | 11.) & ((.01 | 1*)')
|
||||
(.111. | 11. | 1.) & ((.01 )')
|
||||
(.111. | 11. | 1.) & ((.01 | 1*)')
|
||||
(.01 )'
|
||||
(.01 | 1 )'
|
||||
(.01 | ^ )'
|
||||
(.01 | 1*)'
|
||||
(.111.) & ((.01 | 1 )')
|
||||
(.111. | 11.) & ((.01 | ^ )')
|
||||
(.111. | 11.) & ((.01 | 1*)')
|
||||
(.111. | 11. | 1.) & ((.01 )')
|
||||
(.111. | 11. | 1.) & ((.01 | 1*)')
|
||||
|
||||
Larger Alphabets
|
||||
----------------
|
||||
|
||||
We could parse larger alphabets by defining patterns for e.g. each byte
|
||||
We could parse larger alphabets by defining patterns for e.g. each byte
|
||||
of the ASCII code. Or we can generalize this code. If you study the code
|
||||
above you'll see that we never use the "set-ness" of the symbols ``O``
|
||||
above you’ll see that we never use the “set-ness” of the symbols ``O``
|
||||
and ``l``. The only time Python set operators (``&`` and ``|``) appear
|
||||
is in the ``nully()`` function, and there they operate on (recursively
|
||||
computed) outputs of that function, never ``O`` and ``l``.
|
||||
@@ -489,33 +489,33 @@ What if we try:
|
||||
|
||||
::
|
||||
|
||||
(OR, O, l)
|
||||
(OR, O, l)
|
||||
|
||||
∂1((OR, O, l))
|
||||
∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
||||
∂1(O) ∨ ∂1(l)
|
||||
∂a(¬a) → ϕ
|
||||
ϕ ∨ ∂1(l)
|
||||
∂a(a) → λ
|
||||
ϕ ∨ λ
|
||||
ϕ ∨ R = R
|
||||
λ
|
||||
∂1((OR, O, l))
|
||||
∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
||||
∂1(O) ∨ ∂1(l)
|
||||
∂a(¬a) → ϕ
|
||||
ϕ ∨ ∂1(l)
|
||||
∂a(a) → λ
|
||||
ϕ ∨ λ
|
||||
ϕ ∨ R = R
|
||||
λ
|
||||
|
||||
And compare it to:
|
||||
|
||||
::
|
||||
|
||||
{'0', '1')
|
||||
{'0', '1')
|
||||
|
||||
∂1({'0', '1'))
|
||||
∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
||||
∂1({'0')) ∨ ∂1({'1'))
|
||||
∂a(¬a) → ϕ
|
||||
ϕ ∨ ∂1({'1'))
|
||||
∂a(a) → λ
|
||||
ϕ ∨ λ
|
||||
ϕ ∨ R = R
|
||||
λ
|
||||
∂1({'0', '1'))
|
||||
∂a(R ∨ S) → ∂a(R) ∨ ∂a(S)
|
||||
∂1({'0')) ∨ ∂1({'1'))
|
||||
∂a(¬a) → ϕ
|
||||
ϕ ∨ ∂1({'1'))
|
||||
∂a(a) → λ
|
||||
ϕ ∨ λ
|
||||
ϕ ∨ R = R
|
||||
λ
|
||||
|
||||
This suggests that we should be able to alter the functions above to
|
||||
detect sets and deal with them appropriately. Exercise for the Reader
|
||||
@@ -529,9 +529,9 @@ machine transition table.
|
||||
|
||||
::
|
||||
|
||||
.111. & (.01 + 11*)'
|
||||
.111. & (.01 + 11*)'
|
||||
|
||||
Says, "Three or more 1's and not ending in 01 nor composed of all 1's."
|
||||
Says, “Three or more 1’s and not ending in 01 nor composed of all 1’s.”
|
||||
|
||||
.. figure:: attachment:omg.svg
|
||||
:alt: omg.svg
|
||||
@@ -540,32 +540,32 @@ Says, "Three or more 1's and not ending in 01 nor composed of all 1's."
|
||||
|
||||
Start at ``a`` and follow the transition arrows according to their
|
||||
labels. Accepting states have a double outline. (Graphic generated with
|
||||
`Dot from Graphviz <http://www.graphviz.org/>`__.) You'll see that only
|
||||
`Dot from Graphviz <http://www.graphviz.org/>`__.) You’ll see that only
|
||||
paths that lead to one of the accepting states will match the regular
|
||||
expression. All other paths will terminate at one of the non-accepting
|
||||
states.
|
||||
|
||||
There's a happy path to ``g`` along 111:
|
||||
There’s a happy path to ``g`` along 111:
|
||||
|
||||
::
|
||||
|
||||
a→c→e→g
|
||||
a→c→e→g
|
||||
|
||||
After you reach ``g`` you're stuck there eating 1's until you see a 0,
|
||||
which takes you to the ``i→j→i|i→j→h→i`` "trap". You can't reach any
|
||||
After you reach ``g`` you’re stuck there eating 1’s until you see a 0,
|
||||
which takes you to the ``i→j→i|i→j→h→i`` “trap”. You can’t reach any
|
||||
other states from those two loops.
|
||||
|
||||
If you see a 0 before you see 111 you will reach ``b``, which forms
|
||||
another "trap" with ``d`` and ``f``. The only way out is another happy
|
||||
another “trap” with ``d`` and ``f``. The only way out is another happy
|
||||
path along 111 to ``h``:
|
||||
|
||||
::
|
||||
|
||||
b→d→f→h
|
||||
b→d→f→h
|
||||
|
||||
Once you have reached ``h`` you can see as many 1's or as many 0' in a
|
||||
row and still be either still at ``h`` (for 1's) or move to ``i`` (for
|
||||
0's). If you find yourself at ``i`` you can see as many 0's, or
|
||||
Once you have reached ``h`` you can see as many 1’s or as many 0’ in a
|
||||
row and still be either still at ``h`` (for 1’s) or move to ``i`` (for
|
||||
0’s). If you find yourself at ``i`` you can see as many 0’s, or
|
||||
repetitions of 10, as there are, but if you see just a 1 you move to
|
||||
``j``.
|
||||
|
||||
@@ -575,14 +575,14 @@ RE to FSM
|
||||
So how do we get the state machine from the regular expression?
|
||||
|
||||
It turns out that each RE is effectively a state, and each arrow points
|
||||
to the derivative RE in respect to the arrow's symbol.
|
||||
to the derivative RE in respect to the arrow’s symbol.
|
||||
|
||||
If we label the initial RE ``a``, we can say:
|
||||
|
||||
::
|
||||
|
||||
a --0--> ∂0(a)
|
||||
a --1--> ∂1(a)
|
||||
a --0--> ∂0(a)
|
||||
a --1--> ∂1(a)
|
||||
|
||||
And so on, each new unique RE is a new state in the FSM table.
|
||||
|
||||
@@ -590,18 +590,18 @@ Here are the derived REs at each state:
|
||||
|
||||
::
|
||||
|
||||
a = (.111.) & ((.01 | 11*)')
|
||||
b = (.111.) & ((.01 | 1)')
|
||||
c = (.111. | 11.) & ((.01 | 1*)')
|
||||
d = (.111. | 11.) & ((.01 | ^)')
|
||||
e = (.111. | 11. | 1.) & ((.01 | 1*)')
|
||||
f = (.111. | 11. | 1.) & ((.01)')
|
||||
g = (.01 | 1*)'
|
||||
h = (.01)'
|
||||
i = (.01 | 1)'
|
||||
j = (.01 | ^)'
|
||||
a = (.111.) & ((.01 | 11*)')
|
||||
b = (.111.) & ((.01 | 1)')
|
||||
c = (.111. | 11.) & ((.01 | 1*)')
|
||||
d = (.111. | 11.) & ((.01 | ^)')
|
||||
e = (.111. | 11. | 1.) & ((.01 | 1*)')
|
||||
f = (.111. | 11. | 1.) & ((.01)')
|
||||
g = (.01 | 1*)'
|
||||
h = (.01)'
|
||||
i = (.01 | 1)'
|
||||
j = (.01 | ^)'
|
||||
|
||||
You can see the one-way nature of the ``g`` state and the ``hij`` "trap"
|
||||
You can see the one-way nature of the ``g`` state and the ``hij`` “trap”
|
||||
in the way that the ``.111.`` on the left-hand side of the ``&``
|
||||
disappears once it has been matched.
|
||||
|
||||
@@ -764,16 +764,16 @@ Drive a FSM
|
||||
There are *lots* of FSM libraries already. Once you have the state
|
||||
transition table they should all be straightforward to use. State
|
||||
Machine code is very simple. Just for fun, here is an implementation in
|
||||
Python that imitates what "compiled" FSM code might look like in an
|
||||
"unrolled" form. Most FSM code uses a little driver loop and a table
|
||||
Python that imitates what “compiled” FSM code might look like in an
|
||||
“unrolled” form. Most FSM code uses a little driver loop and a table
|
||||
datastructure, the code below instead acts like JMP instructions
|
||||
("jump", or GOTO in higher-level-but-still-low-level languages) to
|
||||
(“jump”, or GOTO in higher-level-but-still-low-level languages) to
|
||||
hard-code the information in the table into a little patch of branches.
|
||||
|
||||
Trampoline Function
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Python has no GOTO statement but we can fake it with a "trampoline"
|
||||
Python has no GOTO statement but we can fake it with a “trampoline”
|
||||
function.
|
||||
|
||||
.. code:: ipython2
|
||||
@@ -790,8 +790,8 @@ function.
|
||||
Stream Functions
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
Little helpers to process the iterator of our data (a "stream" of "1"
|
||||
and "0" characters, not bits.)
|
||||
Little helpers to process the iterator of our data (a “stream” of “1”
|
||||
and “0” characters, not bits.)
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -831,7 +831,7 @@ labels.)
|
||||
|
||||
Note that the implementations of ``h`` and ``g`` are identical ergo
|
||||
``h = g`` and we could eliminate one in the code but ``h`` is an
|
||||
accepting state and ``g`` isn't.
|
||||
accepting state and ``g`` isn’t.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -885,7 +885,7 @@ Reversing the Derivatives to Generate Matching Strings
|
||||
------------------------------------------------------
|
||||
|
||||
(UNFINISHED) Brzozowski also shewed how to go from the state machine to
|
||||
strings and expressions...
|
||||
strings and expressions…
|
||||
|
||||
Each of these states is just a name for a Brzozowskian RE, and so, other
|
||||
than the initial state ``a``, they can can be described in terms of the
|
||||
@@ -893,54 +893,54 @@ derivative-with-respect-to-N of some other state/RE:
|
||||
|
||||
::
|
||||
|
||||
c = d1(a)
|
||||
b = d0(a)
|
||||
b = d0(c)
|
||||
...
|
||||
i = d0(j)
|
||||
j = d1(i)
|
||||
c = d1(a)
|
||||
b = d0(a)
|
||||
b = d0(c)
|
||||
...
|
||||
i = d0(j)
|
||||
j = d1(i)
|
||||
|
||||
Consider:
|
||||
|
||||
::
|
||||
|
||||
c = d1(a)
|
||||
b = d0(c)
|
||||
c = d1(a)
|
||||
b = d0(c)
|
||||
|
||||
Substituting:
|
||||
|
||||
::
|
||||
|
||||
b = d0(d1(a))
|
||||
b = d0(d1(a))
|
||||
|
||||
Unwrapping:
|
||||
|
||||
::
|
||||
|
||||
b = d10(a)
|
||||
b = d10(a)
|
||||
|
||||
'''
|
||||
’’’
|
||||
|
||||
::
|
||||
|
||||
j = d1(d0(j))
|
||||
j = d1(d0(j))
|
||||
|
||||
Unwrapping:
|
||||
|
||||
::
|
||||
|
||||
j = d1(d0(j)) = d01(j)
|
||||
j = d1(d0(j)) = d01(j)
|
||||
|
||||
We have a loop or "fixed point".
|
||||
We have a loop or “fixed point”.
|
||||
|
||||
::
|
||||
|
||||
j = d01(j) = d0101(j) = d010101(j) = ...
|
||||
j = d01(j) = d0101(j) = d010101(j) = ...
|
||||
|
||||
hmm...
|
||||
hmm…
|
||||
|
||||
::
|
||||
|
||||
j = (01)*
|
||||
j = (01)*
|
||||
|
||||
|
||||
|
||||
@@ -11,51 +11,51 @@ Consider the ``x`` combinator:
|
||||
|
||||
::
|
||||
|
||||
x == dup i
|
||||
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
|
||||
[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
|
||||
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]
|
||||
[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]
|
||||
b [a B] rest cons
|
||||
b [B] cons
|
||||
[b B]
|
||||
|
||||
Altogether, this is the definition of ``B``:
|
||||
|
||||
::
|
||||
|
||||
B == swap [C] dip rest cons
|
||||
B == swap [C] dip rest cons
|
||||
|
||||
We can make a generator for the Natural numbers (0, 1, 2, ...) by using
|
||||
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]
|
||||
[0 swap [dup ++] dip rest cons]
|
||||
|
||||
Let's try it:
|
||||
Let’s try it:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -128,32 +128,32 @@ our quoted program:
|
||||
|
||||
::
|
||||
|
||||
a [C] G
|
||||
-------------------------
|
||||
[a swap [C] direco]
|
||||
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]
|
||||
[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
|
||||
G == [direco] cons [swap] swap concat cons
|
||||
G == [direco] cons [swap] swoncat cons
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('G == [direco] cons [swap] swoncat cons')
|
||||
|
||||
Let's try it out:
|
||||
Let’s try it out:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -208,20 +208,20 @@ 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
|
||||
“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
|
||||
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
|
||||
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.
|
||||
@@ -250,7 +250,7 @@ int right two bits.
|
||||
3 3702 .
|
||||
|
||||
|
||||
If we plug ``14811`` and ``[PE1.1]`` into our generator form...
|
||||
If we plug ``14811`` and ``[PE1.1]`` into our generator form…
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -262,8 +262,7 @@ If we plug ``14811`` and ``[PE1.1]`` into our generator form...
|
||||
[14811 swap [PE1.1] direco]
|
||||
|
||||
|
||||
...we get a generator that works for seven cycles before it reaches
|
||||
zero:
|
||||
…we get a generator that works for seven cycles before it reaches zero:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -306,15 +305,15 @@ if so.
|
||||
|
||||
|
||||
(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
|
||||
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
|
||||
five less than 1000. It’s worked out that we need to use all seven
|
||||
numbers sixty-six times and then four more.
|
||||
|
||||
.. code:: ipython2
|
||||
@@ -375,76 +374,76 @@ Consider:
|
||||
|
||||
::
|
||||
|
||||
[b a F] x
|
||||
[b a F] b a F
|
||||
[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
|
||||
[b a F] b a +
|
||||
[b a F] b+a
|
||||
|
||||
From here we want to arrive at:
|
||||
|
||||
::
|
||||
|
||||
b [b+a b F]
|
||||
b [b+a b F]
|
||||
|
||||
Let's start with ``swons``:
|
||||
Let’s start with ``swons``:
|
||||
|
||||
::
|
||||
|
||||
[b a F] b+a swons
|
||||
[b+a b a F]
|
||||
[b a F] b+a swons
|
||||
[b+a b a F]
|
||||
|
||||
Considering this quote as a stack:
|
||||
|
||||
::
|
||||
|
||||
F a b b+a
|
||||
F a b b+a
|
||||
|
||||
We want to get it to:
|
||||
|
||||
::
|
||||
|
||||
F b b+a b
|
||||
F b b+a b
|
||||
|
||||
So:
|
||||
|
||||
::
|
||||
|
||||
F a b b+a popdd over
|
||||
F b b+a b
|
||||
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]
|
||||
[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]
|
||||
[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]
|
||||
[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]
|
||||
F == + [popdd over] cons infra uncons
|
||||
fib_gen == [1 1 F]
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -467,8 +466,8 @@ Putting it all together:
|
||||
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.
|
||||
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
|
||||
@@ -479,13 +478,13 @@ function that adds a term in the sequence to a sum if it is even, and
|
||||
define('PE2.1 == dup 2 % [+] [pop] branch')
|
||||
|
||||
And a predicate function that detects when the terms in the series
|
||||
"exceed four million".
|
||||
“exceed four million”.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('>4M == 4000000 >')
|
||||
|
||||
Now it's straightforward to define ``PE2`` as a recursive function that
|
||||
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.
|
||||
|
||||
@@ -503,18 +502,18 @@ and sums the even ones.
|
||||
4613732
|
||||
|
||||
|
||||
Here's the collected program definitions:
|
||||
Here’s the collected program definitions:
|
||||
|
||||
::
|
||||
|
||||
fib == + swons [popdd over] infra uncons
|
||||
fib_gen == [1 1 fib]
|
||||
fib == + swons [popdd over] infra uncons
|
||||
fib_gen == [1 1 fib]
|
||||
|
||||
even == dup 2 %
|
||||
>4M == 4000000 >
|
||||
even == dup 2 %
|
||||
>4M == 4000000 >
|
||||
|
||||
PE2.1 == even [+] [pop] branch
|
||||
PE2 == 0 fib_gen x [pop >4M] [popop] [[PE2.1] dip x] primrec
|
||||
PE2.1 == even [+] [pop] branch
|
||||
PE2 == 0 fib_gen x [pop >4M] [popop] [[PE2.1] dip x] primrec
|
||||
|
||||
Even-valued Fibonacci Terms
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -523,16 +522,16 @@ Using ``o`` for odd and ``e`` for even:
|
||||
|
||||
::
|
||||
|
||||
o + o = e
|
||||
e + e = e
|
||||
o + e = o
|
||||
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 . . .
|
||||
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.
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ that you start by running the package:
|
||||
|
||||
::
|
||||
|
||||
$ python -m joy
|
||||
Joypy - Copyright © 2017 Simon Forman
|
||||
$ python3 -m joy
|
||||
Thun - Copyright © 2017 Simon Forman
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type "warranty".
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type "sharing" for details.
|
||||
@@ -31,7 +31,7 @@ that you start by running the package:
|
||||
docs for a word.
|
||||
|
||||
|
||||
<-top
|
||||
<-top
|
||||
|
||||
joy? _
|
||||
|
||||
@@ -40,7 +40,14 @@ You can enter Joy notation at the prompt and a :doc:`trace of evaluation <../pre
|
||||
be printed followed by the stack and prompt again::
|
||||
|
||||
joy? 23 sqr 18 +
|
||||
. 23 sqr 18 +
|
||||
|
||||
547 <-top
|
||||
|
||||
joy?
|
||||
|
||||
There is a `trace` combinator::
|
||||
|
||||
joy? 23 [sqr 18 +] trace
|
||||
23 . sqr 18 +
|
||||
23 . dup mul 18 +
|
||||
23 23 . mul 18 +
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
`Newton's method <https://en.wikipedia.org/wiki/Newton%27s_method>`__
|
||||
`Newton’s method <https://en.wikipedia.org/wiki/Newton%27s_method>`__
|
||||
=====================================================================
|
||||
|
||||
Let's use the Newton-Raphson method for finding the root of an equation
|
||||
Let’s use the Newton-Raphson method for finding the root of an equation
|
||||
to write a function that can compute the square root of a number.
|
||||
|
||||
Cf. `"Why Functional Programming Matters" by John
|
||||
Cf. `“Why Functional Programming Matters” by John
|
||||
Hughes <https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf>`__
|
||||
|
||||
.. code:: ipython2
|
||||
@@ -20,9 +20,9 @@ computes the next approximation:
|
||||
|
||||
::
|
||||
|
||||
a F
|
||||
---------
|
||||
a'
|
||||
a F
|
||||
---------
|
||||
a'
|
||||
|
||||
A Function to Compute the Next Approximation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -34,17 +34,17 @@ square root:
|
||||
|
||||
::
|
||||
|
||||
a n over / + 2 /
|
||||
a n a / + 2 /
|
||||
a n/a + 2 /
|
||||
a+n/a 2 /
|
||||
(a+n/a)/2
|
||||
a n over / + 2 /
|
||||
a n a / + 2 /
|
||||
a n/a + 2 /
|
||||
a+n/a 2 /
|
||||
(a+n/a)/2
|
||||
|
||||
The function we want has the argument ``n`` in it:
|
||||
|
||||
::
|
||||
|
||||
F == n over / + 2 /
|
||||
F == n over / + 2 /
|
||||
|
||||
Make it into a Generator
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -53,27 +53,27 @@ Our generator would be created by:
|
||||
|
||||
::
|
||||
|
||||
a [dup F] make_generator
|
||||
a [dup F] make_generator
|
||||
|
||||
With n as part of the function F, but n is the input to the sqrt
|
||||
function we’re writing. If we let 1 be the initial approximation:
|
||||
|
||||
::
|
||||
|
||||
1 n 1 / + 2 /
|
||||
1 n/1 + 2 /
|
||||
1 n + 2 /
|
||||
n+1 2 /
|
||||
(n+1)/2
|
||||
1 n 1 / + 2 /
|
||||
1 n/1 + 2 /
|
||||
1 n + 2 /
|
||||
n+1 2 /
|
||||
(n+1)/2
|
||||
|
||||
The generator can be written as:
|
||||
|
||||
::
|
||||
|
||||
23 1 swap [over / + 2 /] cons [dup] swoncat make_generator
|
||||
1 23 [over / + 2 /] cons [dup] swoncat make_generator
|
||||
1 [23 over / + 2 /] [dup] swoncat make_generator
|
||||
1 [dup 23 over / + 2 /] make_generator
|
||||
23 1 swap [over / + 2 /] cons [dup] swoncat make_generator
|
||||
1 23 [over / + 2 /] cons [dup] swoncat make_generator
|
||||
1 [23 over / + 2 /] [dup] swoncat make_generator
|
||||
1 [dup 23 over / + 2 /] make_generator
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -89,8 +89,8 @@ The generator can be written as:
|
||||
[1 [dup 23 over / + 2 /] codireco]
|
||||
|
||||
|
||||
Let's drive the generator a few time (with the ``x`` combinator) and
|
||||
square the approximation to see how well it works...
|
||||
Let’s drive the generator a few time (with the ``x`` combinator) and
|
||||
square the approximation to see how well it works…
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -105,42 +105,42 @@ square the approximation to see how well it works...
|
||||
Finding Consecutive Approximations within a Tolerance
|
||||
-----------------------------------------------------
|
||||
|
||||
From `"Why Functional Programming Matters" by John
|
||||
From `“Why Functional Programming Matters” by John
|
||||
Hughes <https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf>`__:
|
||||
|
||||
The remainder of a square root finder is a function *within*, which
|
||||
takes a tolerance and a list of approximations and looks down the
|
||||
list for two successive approximations that differ by no more than
|
||||
the given tolerance.
|
||||
The remainder of a square root finder is a function *within*, which
|
||||
takes a tolerance and a list of approximations and looks down the
|
||||
list for two successive approximations that differ by no more than
|
||||
the given tolerance.
|
||||
|
||||
(And note that by “list” he means a lazily-evaluated list.)
|
||||
|
||||
Using the *output* ``[a G]`` of the above generator for square root
|
||||
approximations, and further assuming that the first term a has been
|
||||
generated already and epsilon ε is handy on the stack...
|
||||
generated already and epsilon ε is handy on the stack…
|
||||
|
||||
::
|
||||
|
||||
a [b G] ε within
|
||||
---------------------- a b - abs ε <=
|
||||
b
|
||||
a [b G] ε within
|
||||
---------------------- a b - abs ε <=
|
||||
b
|
||||
|
||||
|
||||
a [b G] ε within
|
||||
---------------------- a b - abs ε >
|
||||
b [c G] ε within
|
||||
a [b G] ε within
|
||||
---------------------- a b - abs ε >
|
||||
b [c G] ε within
|
||||
|
||||
Predicate
|
||||
~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
a [b G] ε [first - abs] dip <=
|
||||
a [b G] first - abs ε <=
|
||||
a b - abs ε <=
|
||||
a-b abs ε <=
|
||||
abs(a-b) ε <=
|
||||
(abs(a-b)<=ε)
|
||||
a [b G] ε [first - abs] dip <=
|
||||
a [b G] first - abs ε <=
|
||||
a b - abs ε <=
|
||||
a-b abs ε <=
|
||||
abs(a-b) ε <=
|
||||
(abs(a-b)<=ε)
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -151,10 +151,10 @@ Base-Case
|
||||
|
||||
::
|
||||
|
||||
a [b G] ε roll< popop first
|
||||
[b G] ε a popop first
|
||||
[b G] first
|
||||
b
|
||||
a [b G] ε roll< popop first
|
||||
[b G] ε a popop first
|
||||
[b G] first
|
||||
b
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -165,7 +165,7 @@ Recur
|
||||
|
||||
::
|
||||
|
||||
a [b G] ε R0 [within] R1
|
||||
a [b G] ε R0 [within] R1
|
||||
|
||||
1. Discard a.
|
||||
2. Use ``x`` combinator to generate next term from ``G``.
|
||||
@@ -175,14 +175,14 @@ Pretty straightforward:
|
||||
|
||||
::
|
||||
|
||||
a [b G] ε R0 [within] R1
|
||||
a [b G] ε [popd x] dip [within] i
|
||||
a [b G] popd x ε [within] i
|
||||
[b G] x ε [within] i
|
||||
b [c G] ε [within] i
|
||||
b [c G] ε within
|
||||
a [b G] ε R0 [within] R1
|
||||
a [b G] ε [popd x] dip [within] i
|
||||
a [b G] popd x ε [within] i
|
||||
[b G] x ε [within] i
|
||||
b [c G] ε [within] i
|
||||
b [c G] ε within
|
||||
|
||||
b [c G] ε within
|
||||
b [c G] ε within
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -196,15 +196,15 @@ The recursive function we have defined so far needs a slight preamble:
|
||||
|
||||
::
|
||||
|
||||
[a G] x ε ...
|
||||
a [b G] ε ...
|
||||
[a G] x ε ...
|
||||
a [b G] ε ...
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('within == x 0.000000001 [_within_P] [_within_B] [_within_R] primrec')
|
||||
define('sqrt == gsra within')
|
||||
|
||||
Try it out...
|
||||
Try it out…
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,9 +10,9 @@ Cf.
|
||||
|
||||
::
|
||||
|
||||
-b ± sqrt(b^2 - 4 * a * c)
|
||||
--------------------------------
|
||||
2 * a
|
||||
-b ± sqrt(b^2 - 4 * a * c)
|
||||
--------------------------------
|
||||
2 * a
|
||||
|
||||
:math:`\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}`
|
||||
|
||||
@@ -28,21 +28,21 @@ a definition without them.
|
||||
|
||||
::
|
||||
|
||||
b neg
|
||||
b neg
|
||||
|
||||
``sqrt(b^2 - 4 * a * c)``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
b sqr 4 a c * * - sqrt
|
||||
b sqr 4 a c * * - sqrt
|
||||
|
||||
``/2a``
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
a 2 * /
|
||||
a 2 * /
|
||||
|
||||
``±``
|
||||
~~~~~
|
||||
@@ -52,14 +52,14 @@ replaces them with their sum and difference.
|
||||
|
||||
::
|
||||
|
||||
pm == [+] [-] cleave popdd
|
||||
pm == [+] [-] cleave popdd
|
||||
|
||||
Putting Them Together
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
b neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
|
||||
b neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
|
||||
|
||||
We use ``app2`` to compute both roots by using a quoted program
|
||||
``[2a /]`` built with ``cons``.
|
||||
@@ -72,20 +72,20 @@ the variables:
|
||||
|
||||
::
|
||||
|
||||
b neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
|
||||
b [neg] dupdip sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
|
||||
b a c [[neg] dupdip sqr 4] dipd * * - sqrt pm a 2 * [/] cons app2
|
||||
b a c a [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
|
||||
b a c over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
|
||||
b neg b sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
|
||||
b [neg] dupdip sqr 4 a c * * - sqrt pm a 2 * [/] cons app2
|
||||
b a c [[neg] dupdip sqr 4] dipd * * - sqrt pm a 2 * [/] cons app2
|
||||
b a c a [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
|
||||
b a c over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2
|
||||
|
||||
The three arguments are to the left, so we can "chop off" everything to
|
||||
the right and say it's the definition of the ``quadratic`` function:
|
||||
The three arguments are to the left, so we can “chop off” everything to
|
||||
the right and say it’s the definition of the ``quadratic`` function:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
define('quadratic == over [[[neg] dupdip sqr 4] dipd * * - sqrt pm] dip 2 * [/] cons app2')
|
||||
|
||||
Let's try it out:
|
||||
Let’s try it out:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
|
||||
@@ -10,44 +10,43 @@ several generic specializations.
|
||||
|
||||
::
|
||||
|
||||
[if] [then] [rec1] [rec2] genrec
|
||||
---------------------------------------------------------------------
|
||||
[if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte
|
||||
[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:
|
||||
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."
|
||||
“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
|
||||
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
|
||||
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
|
||||
... 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.
|
||||
@@ -59,9 +58,9 @@ Primitive recursive functions are those where ``R2 == i``.
|
||||
|
||||
::
|
||||
|
||||
P == [I] [T] [R] primrec
|
||||
== [I] [T] [R [P] i] ifte
|
||||
== [I] [T] [R P] ifte
|
||||
P == [I] [T] [R] primrec
|
||||
== [I] [T] [R [P] i] ifte
|
||||
== [I] [T] [R P] ifte
|
||||
|
||||
`Hylomorphism <https://en.wikipedia.org/wiki/Hylomorphism_%28computer_science%29>`__
|
||||
------------------------------------------------------------------------------------
|
||||
@@ -75,8 +74,8 @@ is a recursive function ``H :: A -> C`` that converts a value of type
|
||||
- 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".
|
||||
- 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.
|
||||
@@ -96,12 +95,12 @@ code.
|
||||
|
||||
return H
|
||||
|
||||
Cf. `"Bananas, Lenses, & Barbed
|
||||
Wire" <http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.125>`__
|
||||
Cf. `“Bananas, Lenses, & Barbed
|
||||
Wire” <http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.125>`__
|
||||
|
||||
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".
|
||||
stored in the Python call stack. This is what is meant by “call stack in
|
||||
the form of a cons list”.
|
||||
|
||||
Hylomorphism in Joy
|
||||
-------------------
|
||||
@@ -111,7 +110,7 @@ hylomorphism combinator ``H`` from constituent parts.
|
||||
|
||||
::
|
||||
|
||||
H == [P] c [G] [F] hylomorphism
|
||||
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
|
||||
@@ -120,37 +119,37 @@ with the base case value ``c``.)
|
||||
|
||||
::
|
||||
|
||||
H == [P] [pop c] [J] ifte
|
||||
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.
|
||||
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
|
||||
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
|
||||
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
|
||||
H == [P] c [G] [F] hylomorphism == [P] [pop c] [G] [dip F] genrec
|
||||
|
||||
Derivation of ``hylomorphism`` combinator
|
||||
-----------------------------------------
|
||||
@@ -160,9 +159,9 @@ arguments out of the pieces given to the ``hylomorphism`` combinator.
|
||||
|
||||
::
|
||||
|
||||
[P] c [G] [F] hylomorphism
|
||||
------------------------------------------
|
||||
[P] [pop c] [G] [dip F] genrec
|
||||
[P] c [G] [F] hylomorphism
|
||||
------------------------------------------
|
||||
[P] [pop c] [G] [dip F] genrec
|
||||
|
||||
Working in reverse:
|
||||
|
||||
@@ -174,17 +173,17 @@ 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
|
||||
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
|
||||
hylomorphism == [unit [pop] swoncat] dipd [dip] swoncat genrec
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -193,7 +192,7 @@ the left so we have a definition for ``hylomorphism``:
|
||||
Example: Finding `Triangular Numbers <https://en.wikipedia.org/wiki/Triangular_number>`__
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Let's write a function that, given a positive integer, returns the sum
|
||||
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``.)
|
||||
|
||||
@@ -208,7 +207,7 @@ To sum a range of integers from 0 to *n* - 1:
|
||||
|
||||
define('triangular_number == [1 <=] 0 [-- dup] [+] hylomorphism')
|
||||
|
||||
Let's try it:
|
||||
Let’s try it:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -236,30 +235,30 @@ 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
|
||||
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
|
||||
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
|
||||
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
|
||||
... a G [H1] dip F w/ a G == a′ b
|
||||
|
||||
... c a G [F] dip H2 a G == b a′
|
||||
... c a G [F] dip H2 a G == b a′
|
||||
|
||||
... a [G] dupdip [H3] dip F a G == a′
|
||||
... a [G] dupdip [H3] dip F a G == a′
|
||||
|
||||
... c a [F] dupdip G H4 a G == a′
|
||||
... c a [F] dupdip G H4 a G == a′
|
||||
|
||||
The following four sections illustrate how these work, omitting the
|
||||
predicate evaluation.
|
||||
@@ -269,31 +268,31 @@ predicate evaluation.
|
||||
|
||||
::
|
||||
|
||||
H1 == [P] [pop c] [G] [dip F] genrec
|
||||
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″
|
||||
... 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
|
||||
``c`` and the continuation “collapses” into the final result using the
|
||||
combiner ``F``.
|
||||
|
||||
``H2``
|
||||
@@ -307,53 +306,53 @@ reverse order.
|
||||
|
||||
::
|
||||
|
||||
H2 == c swap [P] [pop] [G [F] dip] primrec
|
||||
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″
|
||||
... 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
|
||||
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
|
||||
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″
|
||||
... 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``
|
||||
~~~~~~
|
||||
@@ -364,22 +363,22 @@ the form:
|
||||
|
||||
::
|
||||
|
||||
H4 == c swap [P] [pop] [[F] dupdip G] primrec
|
||||
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″
|
||||
... 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
|
||||
-----------
|
||||
@@ -390,13 +389,10 @@ values.
|
||||
|
||||
::
|
||||
|
||||
A == [P] [] [G] [swons] hylomorphism
|
||||
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*.
|
||||
``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.
|
||||
@@ -406,8 +402,8 @@ Each of the above variations can be used to make four slightly different
|
||||
|
||||
::
|
||||
|
||||
H1 == [P] [pop c] [G] [dip F] genrec
|
||||
== [0 <=] [pop []] [-- dup] [dip swons] genrec
|
||||
H1 == [P] [pop c] [G] [dip F] genrec
|
||||
== [0 <=] [pop []] [-- dup] [dip swons] genrec
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -428,8 +424,8 @@ Each of the above variations can be used to make four slightly different
|
||||
|
||||
::
|
||||
|
||||
H2 == c swap [P] [pop] [G [F] dip] primrec
|
||||
== [] swap [0 <=] [pop] [-- dup [swons] dip] primrec
|
||||
H2 == c swap [P] [pop] [G [F] dip] primrec
|
||||
== [] swap [0 <=] [pop] [-- dup [swons] dip] primrec
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -450,8 +446,8 @@ Each of the above variations can be used to make four slightly different
|
||||
|
||||
::
|
||||
|
||||
H3 == [P] [pop c] [[G] dupdip] [dip F] genrec
|
||||
== [0 <=] [pop []] [[--] dupdip] [dip swons] genrec
|
||||
H3 == [P] [pop c] [[G] dupdip] [dip F] genrec
|
||||
== [0 <=] [pop []] [[--] dupdip] [dip swons] genrec
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -472,8 +468,8 @@ Each of the above variations can be used to make four slightly different
|
||||
|
||||
::
|
||||
|
||||
H4 == c swap [P] [pop] [[F] dupdip G ] primrec
|
||||
== [] swap [0 <=] [pop] [[swons] dupdip --] primrec
|
||||
H4 == c swap [P] [pop] [[F] dupdip G ] primrec
|
||||
== [] swap [0 <=] [pop] [[swons] dupdip --] primrec
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -503,7 +499,7 @@ and makes some new value.
|
||||
|
||||
::
|
||||
|
||||
C == [not] c [uncons swap] [F] hylomorphism
|
||||
C == [not] c [uncons swap] [F] hylomorphism
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -513,7 +509,7 @@ An example of a catamorphism is the sum function.
|
||||
|
||||
::
|
||||
|
||||
sum == [not] 0 [swuncons] [+] hylomorphism
|
||||
sum == [not] 0 [swuncons] [+] hylomorphism
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -585,16 +581,16 @@ For the Factorial function:
|
||||
|
||||
::
|
||||
|
||||
H4 == c swap [P] [pop] [[F] dupdip G] primrec
|
||||
H4 == c swap [P] [pop] [[F] dupdip G] primrec
|
||||
|
||||
With:
|
||||
|
||||
::
|
||||
|
||||
c == 1
|
||||
F == *
|
||||
G == --
|
||||
P == 1 <=
|
||||
c == 1
|
||||
F == *
|
||||
G == --
|
||||
P == 1 <=
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -613,31 +609,31 @@ With:
|
||||
Example: ``tails``
|
||||
------------------
|
||||
|
||||
An example of a paramorphism for lists given in the `"Bananas..."
|
||||
An example of a paramorphism for lists given in the `“Bananas…”
|
||||
paper <http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.125>`__
|
||||
is ``tails`` which returns the list of "tails" of a list.
|
||||
is ``tails`` which returns the list of “tails” of a list.
|
||||
|
||||
::
|
||||
|
||||
[1 2 3] tails
|
||||
--------------------
|
||||
[[] [3] [2 3]]
|
||||
[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
|
||||
H2 == c swap [P] [pop] [G [F] dip] primrec
|
||||
|
||||
We would use:
|
||||
|
||||
::
|
||||
|
||||
c == []
|
||||
F == swons
|
||||
G == rest dup
|
||||
P == not
|
||||
c == []
|
||||
F == swons
|
||||
G == rest dup
|
||||
P == not
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -656,39 +652,39 @@ We would use:
|
||||
Conclusion: Patterns of Recursion
|
||||
---------------------------------
|
||||
|
||||
Our story so far...
|
||||
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
|
||||
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
|
||||
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)]
|
||||
|[ (c, F), (G, P) ]| == (|c, F|) • [(G, P)]
|
||||
|
||||
`"Bananas, Lenses, & Barbed
|
||||
Wire" <http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.125>`__
|
||||
`“Bananas, Lenses, & Barbed
|
||||
Wire” <http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.125>`__
|
||||
|
||||
::
|
||||
|
||||
(|...|) [(...)] [<...>]
|
||||
(|...|) [(...)] [<...>]
|
||||
|
||||
I think they are having slightly too much fun with the symbols. However,
|
||||
"Too much is always better than not enough."
|
||||
“Too much is always better than not enough.”
|
||||
|
||||
@@ -4,8 +4,8 @@ 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
|
||||
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.
|
||||
|
||||
@@ -74,8 +74,8 @@ Both ``sum`` and ``size`` each convert a sequence to a single value.
|
||||
|
||||
::
|
||||
|
||||
sum == 0 swap [+] step
|
||||
size == 0 swap [pop ++] step
|
||||
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
|
||||
|
||||
@@ -17,10 +17,10 @@ symbols together, juxtaposition:
|
||||
|
||||
::
|
||||
|
||||
foo bar
|
||||
foo bar
|
||||
|
||||
Operations have inputs and outputs. The outputs of ``foo`` must be
|
||||
compatible in "arity", type, and shape with the inputs of ``bar``.
|
||||
compatible in “arity”, type, and shape with the inputs of ``bar``.
|
||||
|
||||
Branch
|
||||
------
|
||||
@@ -29,72 +29,72 @@ Do one thing or another.
|
||||
|
||||
::
|
||||
|
||||
boolean [F] [T] branch
|
||||
boolean [F] [T] branch
|
||||
|
||||
|
||||
t [F] [T] branch
|
||||
----------------------
|
||||
T
|
||||
t [F] [T] branch
|
||||
----------------------
|
||||
T
|
||||
|
||||
|
||||
f [F] [T] branch
|
||||
----------------------
|
||||
F
|
||||
f [F] [T] branch
|
||||
----------------------
|
||||
F
|
||||
|
||||
|
||||
branch == unit cons swap pick i
|
||||
branch == unit cons swap pick i
|
||||
|
||||
boolean [F] [T] branch
|
||||
boolean [F] [T] unit cons swap pick i
|
||||
boolean [F] [[T]] cons swap pick i
|
||||
boolean [[F] [T]] swap pick i
|
||||
[[F] [T]] boolean pick i
|
||||
[F-or-T] i
|
||||
boolean [F] [T] branch
|
||||
boolean [F] [T] unit cons swap pick i
|
||||
boolean [F] [[T]] cons swap pick i
|
||||
boolean [[F] [T]] swap pick i
|
||||
[[F] [T]] boolean pick i
|
||||
[F-or-T] i
|
||||
|
||||
Given some branch function ``G``:
|
||||
|
||||
::
|
||||
|
||||
G == [F] [T] branch
|
||||
G == [F] [T] branch
|
||||
|
||||
Used in a sequence like so:
|
||||
|
||||
::
|
||||
|
||||
foo G bar
|
||||
foo G bar
|
||||
|
||||
The inputs and outputs of ``F`` and ``T`` must be compatible with the
|
||||
outputs for ``foo`` and the inputs of ``bar``, respectively.
|
||||
|
||||
::
|
||||
|
||||
foo F bar
|
||||
foo F bar
|
||||
|
||||
foo T bar
|
||||
foo T bar
|
||||
|
||||
``ifte``
|
||||
~~~~~~~~
|
||||
|
||||
Often it will be easier on the programmer to write branching code with
|
||||
the predicate specified in a quote. The ``ifte`` combinator provides
|
||||
this (``T`` for "then" and ``E`` for "else"):
|
||||
this (``T`` for “then” and ``E`` for “else”):
|
||||
|
||||
::
|
||||
|
||||
[P] [T] [E] ifte
|
||||
[P] [T] [E] ifte
|
||||
|
||||
Defined in terms of ``branch``:
|
||||
|
||||
::
|
||||
|
||||
ifte == [nullary not] dip branch
|
||||
ifte == [nullary not] dip branch
|
||||
|
||||
In this case, ``P`` must be compatible with the stack and return a
|
||||
Boolean value, and ``T`` and ``E`` both must be compatible with the
|
||||
preceeding and following functions, as described above for ``F`` and
|
||||
``T``. (Note that in the current implementation we are depending on
|
||||
Python for the underlying semantics, so the Boolean value doesn't *have*
|
||||
to be Boolean because Python's rules for "truthiness" will be used to
|
||||
Python for the underlying semantics, so the Boolean value doesn’t *have*
|
||||
to be Boolean because Python’s rules for “truthiness” will be used to
|
||||
evaluate it. I reflect this in the structure of the stack effect comment
|
||||
of ``branch``, it will only accept Boolean values, and in the definition
|
||||
of ``ifte`` above by including ``not`` in the quote, which also has the
|
||||
@@ -107,17 +107,17 @@ Do one thing zero or more times.
|
||||
|
||||
::
|
||||
|
||||
boolean [Q] loop
|
||||
boolean [Q] loop
|
||||
|
||||
|
||||
t [Q] loop
|
||||
----------------
|
||||
Q [Q] loop
|
||||
t [Q] loop
|
||||
----------------
|
||||
Q [Q] loop
|
||||
|
||||
|
||||
... f [Q] loop
|
||||
--------------------
|
||||
...
|
||||
... f [Q] loop
|
||||
--------------------
|
||||
...
|
||||
|
||||
The ``loop`` combinator generates a copy of itself in the true branch.
|
||||
This is the hallmark of recursive defintions. In Thun there is no
|
||||
@@ -128,21 +128,21 @@ constructs that do not need to be directly self-referential, unlike
|
||||
|
||||
::
|
||||
|
||||
loop == [] swap [dup dip loop] cons branch
|
||||
loop == [] swap [dup dip loop] cons branch
|
||||
|
||||
boolean [Q] loop
|
||||
boolean [Q] [] swap [dup dip loop] cons branch
|
||||
boolean [] [Q] [dup dip loop] cons branch
|
||||
boolean [] [[Q] dup dip loop] branch
|
||||
boolean [Q] loop
|
||||
boolean [Q] [] swap [dup dip loop] cons branch
|
||||
boolean [] [Q] [dup dip loop] cons branch
|
||||
boolean [] [[Q] dup dip loop] branch
|
||||
|
||||
In action the false branch does nothing while the true branch does:
|
||||
|
||||
::
|
||||
|
||||
t [] [[Q] dup dip loop] branch
|
||||
[Q] dup dip loop
|
||||
[Q] [Q] dip loop
|
||||
Q [Q] loop
|
||||
t [] [[Q] dup dip loop] branch
|
||||
[Q] dup dip loop
|
||||
[Q] [Q] dip loop
|
||||
Q [Q] loop
|
||||
|
||||
Because ``loop`` expects and consumes a Boolean value, the ``Q``
|
||||
function must be compatible with the previous stack *and itself* with a
|
||||
@@ -150,15 +150,15 @@ boolean flag for the next iteration:
|
||||
|
||||
::
|
||||
|
||||
Q == G b
|
||||
Q == G b
|
||||
|
||||
Q [Q] loop
|
||||
G b [Q] loop
|
||||
G Q [Q] loop
|
||||
G G b [Q] loop
|
||||
G G Q [Q] loop
|
||||
G G G b [Q] loop
|
||||
G G G
|
||||
Q [Q] loop
|
||||
G b [Q] loop
|
||||
G Q [Q] loop
|
||||
G G b [Q] loop
|
||||
G G Q [Q] loop
|
||||
G G G b [Q] loop
|
||||
G G G
|
||||
|
||||
``while``
|
||||
~~~~~~~~~
|
||||
@@ -170,21 +170,21 @@ flag for the next iteration:
|
||||
|
||||
::
|
||||
|
||||
[P] [B] while
|
||||
--------------------------------------
|
||||
[P] nullary [B [P] nullary] loop
|
||||
[P] [B] while
|
||||
--------------------------------------
|
||||
[P] nullary [B [P] nullary] loop
|
||||
|
||||
|
||||
while == swap [nullary] cons dup dipd concat loop
|
||||
while == swap [nullary] cons dup dipd concat loop
|
||||
|
||||
|
||||
[P] [B] while
|
||||
[P] [B] swap [nullary] cons dup dipd concat loop
|
||||
[B] [P] [nullary] cons dup dipd concat loop
|
||||
[B] [[P] nullary] dup dipd concat loop
|
||||
[B] [[P] nullary] [[P] nullary] dipd concat loop
|
||||
[P] nullary [B] [[P] nullary] concat loop
|
||||
[P] nullary [B [P] nullary] loop
|
||||
[P] [B] while
|
||||
[P] [B] swap [nullary] cons dup dipd concat loop
|
||||
[B] [P] [nullary] cons dup dipd concat loop
|
||||
[B] [[P] nullary] dup dipd concat loop
|
||||
[B] [[P] nullary] [[P] nullary] dipd concat loop
|
||||
[P] nullary [B] [[P] nullary] concat loop
|
||||
[P] nullary [B [P] nullary] loop
|
||||
|
||||
Parallel
|
||||
--------
|
||||
@@ -192,11 +192,11 @@ Parallel
|
||||
The *parallel* operation indicates that two (or more) functions *do not
|
||||
interfere* with each other and so can run in parallel. The main
|
||||
difficulty in this sort of thing is orchestrating the recombining
|
||||
("join" or "wait") of the results of the functions after they finish.
|
||||
(“join” or “wait”) of the results of the functions after they finish.
|
||||
|
||||
The current implementaions and the following definitions *are not
|
||||
actually parallel* (yet), but there is no reason they couldn't be
|
||||
reimplemented in terms of e.g. Python threads. I am not concerned with
|
||||
actually parallel* (yet), but there is no reason they couldn’t be
|
||||
reimplemented in terms of e.g. Python threads. I am not concerned with
|
||||
performance of the system just yet, only the elegance of the code it
|
||||
allows us to write.
|
||||
|
||||
@@ -207,27 +207,27 @@ Joy has a few parallel combinators, the main one being ``cleave``:
|
||||
|
||||
::
|
||||
|
||||
... x [A] [B] cleave
|
||||
---------------------------------------------------------
|
||||
... [x ...] [A] infra first [x ...] [B] infra first
|
||||
---------------------------------------------------------
|
||||
... a b
|
||||
... x [A] [B] cleave
|
||||
---------------------------------------------------------
|
||||
... [x ...] [A] infra first [x ...] [B] infra first
|
||||
---------------------------------------------------------
|
||||
... a b
|
||||
|
||||
The ``cleave`` combinator expects a value and two quotes and it executes
|
||||
each quote in "separate universes" such that neither can affect the
|
||||
each quote in “separate universes” such that neither can affect the
|
||||
other, then it takes the first item from the stack in each universe and
|
||||
replaces the value and quotes with their respective results.
|
||||
|
||||
(I think this corresponds to the "fork" operator, the little
|
||||
(I think this corresponds to the “fork” operator, the little
|
||||
upward-pointed triangle, that takes two functions ``A :: x -> a`` and
|
||||
``B :: x -> b`` and returns a function ``F :: x -> (a, b)``, in Conal
|
||||
Elliott's "Compiling to Categories" paper, et. al.)
|
||||
Elliott’s “Compiling to Categories” paper, et. al.)
|
||||
|
||||
Just a thought, if you ``cleave`` two jobs and one requires more time to
|
||||
finish than the other you'd like to be able to assign resources
|
||||
finish than the other you’d like to be able to assign resources
|
||||
accordingly so that they both finish at the same time.
|
||||
|
||||
"Apply" Functions
|
||||
“Apply” Functions
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are also ``app2`` and ``app3`` which run a single quote on more
|
||||
@@ -235,35 +235,35 @@ than one value:
|
||||
|
||||
::
|
||||
|
||||
... y x [Q] app2
|
||||
---------------------------------------------------------
|
||||
... [y ...] [Q] infra first [x ...] [Q] infra first
|
||||
... y x [Q] app2
|
||||
---------------------------------------------------------
|
||||
... [y ...] [Q] infra first [x ...] [Q] infra first
|
||||
|
||||
|
||||
... z y x [Q] app3
|
||||
---------------------------------
|
||||
... [z ...] [Q] infra first
|
||||
[y ...] [Q] infra first
|
||||
[x ...] [Q] infra first
|
||||
... z y x [Q] app3
|
||||
---------------------------------
|
||||
... [z ...] [Q] infra first
|
||||
[y ...] [Q] infra first
|
||||
[x ...] [Q] infra first
|
||||
|
||||
Because the quoted program can be ``i`` we can define ``cleave`` in
|
||||
terms of ``app2``:
|
||||
|
||||
::
|
||||
|
||||
cleave == [i] app2 [popd] dip
|
||||
cleave == [i] app2 [popd] dip
|
||||
|
||||
(I'm not sure why ``cleave`` was specified to take that value, I may
|
||||
(I’m not sure why ``cleave`` was specified to take that value, I may
|
||||
make a combinator that does the same thing but without expecting a
|
||||
value.)
|
||||
|
||||
::
|
||||
|
||||
clv == [i] app2
|
||||
clv == [i] app2
|
||||
|
||||
[A] [B] clv
|
||||
------------------
|
||||
a b
|
||||
[A] [B] clv
|
||||
------------------
|
||||
a b
|
||||
|
||||
``map``
|
||||
~~~~~~~
|
||||
@@ -273,10 +273,10 @@ The common ``map`` function in Joy should also be though of as a
|
||||
|
||||
::
|
||||
|
||||
[a b c ...] [Q] map
|
||||
[a b c ...] [Q] map
|
||||
|
||||
There is no reason why the implementation of ``map`` couldn't distribute
|
||||
the ``Q`` function over e.g. a pool of worker CPUs.
|
||||
There is no reason why the implementation of ``map`` couldn’t distribute
|
||||
the ``Q`` function over e.g. a pool of worker CPUs.
|
||||
|
||||
``pam``
|
||||
~~~~~~~
|
||||
@@ -285,16 +285,16 @@ One of my favorite combinators, the ``pam`` combinator is just:
|
||||
|
||||
::
|
||||
|
||||
pam == [i] map
|
||||
pam == [i] map
|
||||
|
||||
This can be used to run any number of programs separately on the current
|
||||
stack and combine their (first) outputs in a result list.
|
||||
|
||||
::
|
||||
|
||||
[[A] [B] [C] ...] [i] map
|
||||
-------------------------------
|
||||
[ a b c ...]
|
||||
[[A] [B] [C] ...] [i] map
|
||||
-------------------------------
|
||||
[ a b c ...]
|
||||
|
||||
Handling Other Kinds of Join
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -302,7 +302,7 @@ Handling Other Kinds of Join
|
||||
The ``cleave`` operators and others all have pretty brutal join
|
||||
semantics: everything works and we always wait for every
|
||||
sub-computation. We can imagine a few different potentially useful
|
||||
patterns of "joining" results from parallel combinators.
|
||||
patterns of “joining” results from parallel combinators.
|
||||
|
||||
first-to-finish
|
||||
^^^^^^^^^^^^^^^
|
||||
@@ -313,24 +313,24 @@ stack could be replaced by its output stack.
|
||||
|
||||
The other sub-programs would be cancelled.
|
||||
|
||||
"Fulminators"
|
||||
“Fulminators”
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Also known as "Futures" or "Promises" (by *everybody* else. "Fulinators"
|
||||
Also known as “Futures” or “Promises” (by *everybody* else. “Fulinators”
|
||||
is what I was going to call them when I was thinking about implementing
|
||||
them in Thun.)
|
||||
|
||||
The runtime could be amended to permit "thunks" representing the results
|
||||
The runtime could be amended to permit “thunks” representing the results
|
||||
of in-progress computations to be left on the stack and picked up by
|
||||
subsequent functions. These would themselves be able to leave behind
|
||||
more "thunks", the values of which depend on the eventual resolution of
|
||||
more “thunks”, the values of which depend on the eventual resolution of
|
||||
the values of the previous thunks.
|
||||
|
||||
In this way you can create "chains" (and more complex shapes) out of
|
||||
In this way you can create “chains” (and more complex shapes) out of
|
||||
normal-looking code that consist of a kind of call-graph interspersed
|
||||
with "asyncronous" ... events?
|
||||
with “asyncronous” … events?
|
||||
|
||||
In any case, until I can find a rigorous theory that shows that this
|
||||
sort of thing works perfectly in Joy code I'm not going to worry about
|
||||
sort of thing works perfectly in Joy code I’m not going to worry about
|
||||
it. (And I think the Categories can deal with it anyhow? Incremental
|
||||
evaluation, yeah?)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
Treating Trees II: ``treestep``
|
||||
===============================
|
||||
|
||||
Let's consider a tree structure, similar to one described `"Why
|
||||
functional programming matters" by John
|
||||
Let’s consider a tree structure, similar to one described `“Why
|
||||
functional programming matters” by John
|
||||
Hughes <https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf>`__,
|
||||
that consists of a node value followed by zero or more child trees. (The
|
||||
asterisk is meant to indicate the `Kleene
|
||||
@@ -10,7 +10,7 @@ star <https://en.wikipedia.org/wiki/Kleene_star>`__.)
|
||||
|
||||
::
|
||||
|
||||
tree = [] | [node tree*]
|
||||
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
|
||||
@@ -18,15 +18,15 @@ base-case function ``[B]``, and two quoted programs ``[N]`` and ``[C]``.
|
||||
|
||||
::
|
||||
|
||||
tree [B] [N] [C] treestep
|
||||
tree [B] [N] [C] treestep
|
||||
|
||||
If the current tree node is empty then just execute ``B``:
|
||||
|
||||
::
|
||||
|
||||
[] [B] [N] [C] treestep
|
||||
---------------------------
|
||||
[] 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
|
||||
@@ -34,11 +34,11 @@ combine the result with ``C``.
|
||||
|
||||
::
|
||||
|
||||
[node tree*] [B] [N] [C] treestep
|
||||
--------------------------------------- w/ K == [B] [N] [C] treestep
|
||||
node N [tree*] [K] map 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
|
||||
(Later on we’ll experiment with making ``map`` part of ``C`` so you can
|
||||
use other combinators.)
|
||||
|
||||
Derive the recursive function.
|
||||
@@ -49,59 +49,59 @@ will produce.
|
||||
|
||||
::
|
||||
|
||||
K == [not] [B] [R0] [R1] genrec
|
||||
== [not] [B] [R0 [K] R1] ifte
|
||||
K == [not] [B] [R0] [R1] genrec
|
||||
== [not] [B] [R0 [K] R1] ifte
|
||||
|
||||
So we just have to derive ``J``:
|
||||
|
||||
::
|
||||
|
||||
J == R0 [K] R1
|
||||
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
|
||||
[node tree*] J
|
||||
------------------------------
|
||||
node N [tree*] [K] map C
|
||||
|
||||
So ``J`` will have some form like:
|
||||
|
||||
::
|
||||
|
||||
J == ... [N] ... [K] ... [C] ...
|
||||
J == ... [N] ... [K] ... [C] ...
|
||||
|
||||
Let's dive in. First, unquote the node and ``dip`` ``N``.
|
||||
Let’s dive in. First, unquote the node and ``dip`` ``N``.
|
||||
|
||||
::
|
||||
|
||||
[node tree*] uncons [N] dip
|
||||
node [tree*] [N] dip
|
||||
node N [tree*]
|
||||
[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
|
||||
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
|
||||
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
|
||||
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.
|
||||
-----------------------------------------------
|
||||
@@ -110,26 +110,26 @@ 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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
[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
|
||||
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
|
||||
[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.
|
||||
|
||||
@@ -140,10 +140,10 @@ 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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
[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``
|
||||
-------------------
|
||||
@@ -171,7 +171,7 @@ all nodes in a tree with this function:
|
||||
|
||||
::
|
||||
|
||||
sumtree == [pop 0] [] [sum +] treestep
|
||||
sumtree == [pop 0] [] [sum +] treestep
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -181,9 +181,9 @@ Running this function on an empty tree value gives zero:
|
||||
|
||||
::
|
||||
|
||||
[] [pop 0] [] [sum +] treestep
|
||||
------------------------------------
|
||||
0
|
||||
[] [pop 0] [] [sum +] treestep
|
||||
------------------------------------
|
||||
0
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -199,11 +199,11 @@ 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
|
||||
[n tree*] [pop 0] [] [sum +] treestep
|
||||
n [tree*] [[pop 0] [] [sum +] treestep] map sum +
|
||||
n [ ... ] sum +
|
||||
n m +
|
||||
n+m
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -310,7 +310,7 @@ Redefining the Ordered Binary Tree in terms of ``treestep``.
|
||||
|
||||
::
|
||||
|
||||
Tree = [] | [[key value] left right]
|
||||
Tree = [] | [[key value] left right]
|
||||
|
||||
What kind of functions can we write for this with our ``treestep``?
|
||||
|
||||
@@ -318,26 +318,26 @@ The pattern for processing a non-empty node is:
|
||||
|
||||
::
|
||||
|
||||
node N [tree*] [K] map C
|
||||
node N [tree*] [K] map C
|
||||
|
||||
Plugging in our BTree structure:
|
||||
|
||||
::
|
||||
|
||||
[key value] N [left right] [K] map C
|
||||
[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
|
||||
[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:
|
||||
This doesn’t quite work:
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -349,25 +349,25 @@ This doesn't quite work:
|
||||
3 'B' 'B'
|
||||
|
||||
|
||||
Doesn't work because ``map`` extracts the ``first`` item of whatever its
|
||||
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] 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 ]
|
||||
[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
|
||||
[] [first] [flatten cons] treestep
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -388,18 +388,18 @@ 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]
|
||||
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
|
||||
[] [i roll< swons concat] [first] treestep
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -414,20 +414,20 @@ So:
|
||||
With ``treegrind``?
|
||||
-------------------
|
||||
|
||||
The ``treegrind`` function doesn't include the ``map`` combinator, so
|
||||
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
|
||||
node N [tree*] [K] C
|
||||
|
||||
Plugging in our BTree structure:
|
||||
|
||||
::
|
||||
|
||||
[key value] N [left right] [K] C
|
||||
[key value] N [left right] [K] C
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -454,7 +454,7 @@ Iteration through the nodes
|
||||
[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.
|
||||
Sum the nodes’ keys.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -485,28 +485,28 @@ I think we do:
|
||||
|
||||
::
|
||||
|
||||
[B] [N] [C] treegrind
|
||||
[B] [N] [C] treegrind
|
||||
|
||||
We'll start by saying that the base-case (the key is not in the tree) is
|
||||
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
|
||||
[B] [query_key] [C] treegrind
|
||||
|
||||
This means we just have to define ``C`` from:
|
||||
|
||||
::
|
||||
|
||||
[key value] query_key [left right] [K] C
|
||||
[key value] query_key [left right] [K] C
|
||||
|
||||
Let's try ``cmp``:
|
||||
Let’s try ``cmp``:
|
||||
|
||||
::
|
||||
|
||||
C == P [T>] [E] [T<] cmp
|
||||
C == P [T>] [E] [T<] cmp
|
||||
|
||||
[key value] query_key [left right] [K] P [T>] [E] [T<] cmp
|
||||
[key value] query_key [left right] [K] P [T>] [E] [T<] cmp
|
||||
|
||||
The predicate ``P``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
@@ -516,16 +516,16 @@ 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] 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
|
||||
[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
|
||||
P == roll< [roll< uncons swap] dip
|
||||
|
||||
(Possibly with a swap at the end? Or just swap ``T<`` and ``T>``.)
|
||||
|
||||
@@ -533,15 +533,15 @@ So now:
|
||||
|
||||
::
|
||||
|
||||
[left right] [K] [value] key query_key [T>] [E] [T<] cmp
|
||||
[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<
|
||||
[left right] [K] [value] T>
|
||||
[left right] [K] [value] E
|
||||
[left right] [K] [value] T<
|
||||
|
||||
``E``
|
||||
~~~~~
|
||||
@@ -550,27 +550,27 @@ Easy.
|
||||
|
||||
::
|
||||
|
||||
E == roll> popop first
|
||||
E == roll> popop first
|
||||
|
||||
``T<`` and ``T>``
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
T< == pop [first] dip i
|
||||
T> == pop [second] dip i
|
||||
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
|
||||
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
|
||||
Tree-get == [P [T>] [E] [T<] cmp] treegrind
|
||||
|
||||
To me, that seems simpler than the ``genrec`` version.
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ An Example
|
||||
(... [3 4 ] 2 1 0 -- ... [1 2 ])
|
||||
|
||||
|
||||
Unification Works "in Reverse"
|
||||
Unification Works “in Reverse”
|
||||
------------------------------
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,14 @@
|
||||
Traversing Datastructures with Zippers
|
||||
======================================
|
||||
|
||||
This notebook is about using the "zipper" with joy datastructures. See
|
||||
This notebook is about using the “zipper” with joy datastructures. See
|
||||
the `Zipper wikipedia
|
||||
entry <https://en.wikipedia.org/wiki/Zipper_%28data_structure%29>`__ or
|
||||
the original paper: `"FUNCTIONAL PEARL The Zipper" by Gérard
|
||||
the original paper: `“FUNCTIONAL PEARL The Zipper” by Gérard
|
||||
Huet <https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf>`__
|
||||
|
||||
Given a datastructure on the stack we can navigate through it, modify
|
||||
it, and rebuild it using the "zipper" technique.
|
||||
it, and rebuild it using the “zipper” technique.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -17,10 +17,9 @@ it, and rebuild it using the "zipper" technique.
|
||||
Trees
|
||||
-----
|
||||
|
||||
In Joypy there aren't any complex datastructures, just ints, floats,
|
||||
In Joypy there aren’t any complex datastructures, just ints, floats,
|
||||
strings, Symbols (strings that are names of functions) and sequences
|
||||
(aka lists, aka quoted literals, aka aggregates, etc...), but we can
|
||||
build
|
||||
(aka lists, aka quoted literals, aka aggregates, etc…), but we can build
|
||||
`trees <https://en.wikipedia.org/wiki/Tree_%28data_structure%29>`__ out
|
||||
of sequences.
|
||||
|
||||
@@ -45,12 +44,12 @@ In Joy we can do this with the following words:
|
||||
|
||||
::
|
||||
|
||||
z-down == [] swap uncons swap
|
||||
z-up == swons swap shunt
|
||||
z-right == [swons] cons dip uncons swap
|
||||
z-left == swons [uncons swap] dip swap
|
||||
z-down == [] swap uncons swap
|
||||
z-up == swons swap shunt
|
||||
z-right == [swons] cons dip uncons swap
|
||||
z-left == swons [uncons swap] dip swap
|
||||
|
||||
Let's use them to change 25 into 625. The first time a word is used I
|
||||
Let’s use them to change 25 into 625. The first time a word is used I
|
||||
show the trace so you can see how it works. If we were going to use
|
||||
these a lot it would make sense to write Python versions for efficiency,
|
||||
but see below.
|
||||
@@ -208,8 +207,8 @@ but see below.
|
||||
``dip`` and ``infra``
|
||||
---------------------
|
||||
|
||||
In Joy we have the ``dip`` and ``infra`` combinators which can "target"
|
||||
or "address" any particular item in a Joy tree structure.
|
||||
In Joy we have the ``dip`` and ``infra`` combinators which can “target”
|
||||
or “address” any particular item in a Joy tree structure.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -247,8 +246,8 @@ or "address" any particular item in a Joy tree structure.
|
||||
[1 [2 [3 4 625 6] 7] 8] .
|
||||
|
||||
|
||||
If you read the trace carefully you'll see that about half of it is the
|
||||
``dip`` and ``infra`` combinators de-quoting programs and "digging" into
|
||||
If you read the trace carefully you’ll see that about half of it is the
|
||||
``dip`` and ``infra`` combinators de-quoting programs and “digging” into
|
||||
the subject datastructure. Instead of maintaining temporary results on
|
||||
the stack they are pushed into the pending expression (continuation).
|
||||
When ``sqr`` has run the rest of the pending expression rebuilds the
|
||||
@@ -264,12 +263,12 @@ been embedded in a nested series of quoted programs, e.g.:
|
||||
|
||||
::
|
||||
|
||||
[...] [Q] [dip dip infra dip infra dip infra] Z
|
||||
-------------------------------------------------------------
|
||||
[...] [[[[[[[Q] dip] dip] infra] dip] infra] dip] infra
|
||||
|
||||
[...] [Q] [dip dip infra dip infra dip infra] Z
|
||||
-------------------------------------------------------------
|
||||
[...] [[[[[[[Q] dip] dip] infra] dip] infra] dip] infra
|
||||
|
||||
|
||||
The ``Z`` function isn't hard to make.
|
||||
The ``Z`` function isn’t hard to make.
|
||||
|
||||
.. code:: ipython2
|
||||
|
||||
@@ -333,21 +332,21 @@ a string made from only two characters.
|
||||
|
||||
::
|
||||
|
||||
[...] [Q] 'ddididi' Zstr
|
||||
-------------------------------------------------------------
|
||||
[...] [[[[[[[Q] dip] dip] infra] dip] infra] dip] infra
|
||||
[...] [Q] 'ddididi' Zstr
|
||||
-------------------------------------------------------------
|
||||
[...] [[[[[[[Q] dip] dip] infra] dip] infra] dip] infra
|
||||
|
||||
The string can be considered a name or address for an item in the
|
||||
subject datastructure.
|
||||
|
||||
Determining the right "path" for an item in a tree.
|
||||
Determining the right “path” for an item in a tree.
|
||||
---------------------------------------------------
|
||||
|
||||
It's easy to read off (in reverse) the right sequence of "d" and "i"
|
||||
It’s easy to read off (in reverse) the right sequence of “d” and “i”
|
||||
from the subject datastructure:
|
||||
|
||||
::
|
||||
|
||||
[ n [ n [ n n x ...
|
||||
i d i d i d d Bingo!
|
||||
[ n [ n [ n n x ...
|
||||
i d i d i d d Bingo!
|
||||
|
||||
|
||||
Reference in New Issue
Block a user