Thun/docs/html/notebooks/Generator_Programs.html

347 lines
12 KiB
HTML

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Generator Programs</title>
<link rel="stylesheet" href="/css/fonts.css">
<link rel="stylesheet" href="/css/site.css">
</head>
<body>
<h1>Generator Programs</h1>
<h2>Using <code>x</code> to Generate Values</h2>
<p>Cf. <a href="https://www.kevinalbrecht.com/code/joy-mirror/jp-reprod.html">Self-reproducing and reproducing programs</a> by Manfred von Thun</p>
<p>Consider the <code>x</code> combinator:</p>
<pre><code>x == dup i
</code></pre>
<p>We can apply it to a quoted program consisting of some value <code>a</code> and some function <code>B</code>:</p>
<pre><code>[a B] x
[a B] a B
</code></pre>
<p>Let <code>B</code> function <code>swap</code> the <code>a</code> with the quote and run some function <code>C</code> on it to generate a new value <code>b</code>:</p>
<pre><code>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]
</code></pre>
<p>Now discard the quoted <code>a</code> with <code>rest</code> then <code>cons</code> <code>b</code>:</p>
<pre><code>b [a B] rest cons
b [B] cons
[b B]
</code></pre>
<p>Altogether, this is the definition of <code>B</code>:</p>
<pre><code>B == swap [C] dip rest cons
</code></pre>
<h2>An Example</h2>
<p>We can make a generator for the Natural numbers (0, 1, 2, ...) by using
<code>0</code> for the initial state <code>a</code> and <code>[dup ++]</code> for <code>[C]</code>.
We need the <code>dup</code> to leave the old state value behind on the stack.
Putting it together:</p>
<pre><code>[0 swap [dup ++] dip rest cons]
</code></pre>
<p>Let's try it:</p>
<pre><code>joy? [0 swap [dup ++] dip rest cons]
[0 swap [dup ++] dip rest cons]
joy? [x]
[0 swap [dup ++] dip rest cons] [x]
joy? trace
[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 0 • 1 + [0 swap [dup ++] dip rest cons] rest cons
0 0 1 • + [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] •
</code></pre>
<p>After one application of <code>x</code> the quoted program contains 1 and 0 is below it on the stack.</p>
<pre><code>0 [1 swap [dup ++] dip rest cons]
</code></pre>
<p>We can use <code>x</code> as many times as we like to get as many terms as we like:</p>
<pre><code>joy? x x x x x pop
0 1 2 3 4 5
</code></pre>
<h3><code>direco</code></h3>
<p>Let's define a helper function:</p>
<pre><code>[direco dip rest cons] inscribe
</code></pre>
<p>That makes our generator quote into:</p>
<pre><code>[0 swap [dup ++] direco]
</code></pre>
<h2>Making Generators</h2>
<p>We want to define a function that accepts <code>a</code> and <code>[C]</code> and builds our quoted program:</p>
<pre><code> a [C] G
-------------------------
[a swap [C] direco]
</code></pre>
<p>Working in reverse:</p>
<pre><code>[a swap [C] direco] cons
a [swap [C] direco] concat
a [swap] [[C] direco] swap
a [[C] direco] [swap]
a [C] [direco] cons [swap]
</code></pre>
<p>Reading from the bottom up:</p>
<pre><code>[direco] cons [swap] swap concat cons
</code></pre>
<p>Or:</p>
<pre><code>[direco] cons [swap] swoncat cons
</code></pre>
<h3>make-generator</h3>
<pre><code>[make-generator [direco] cons [swap] swoncat cons] inscribe
</code></pre>
<p>Let's try it out:</p>
<pre><code>joy? 0 [dup ++] make-generator
[0 swap [dup ++] direco]
</code></pre>
<p>And generate some values:</p>
<pre><code>joy? x x x pop
0 1 2
</code></pre>
<h3>Powers of Two</h3>
<p>Let's generate powers of two:</p>
<pre><code>joy? 1 [dup 1 &lt;&lt;] make-generator
[1 swap [dup 1 &lt;&lt;] direco]
</code></pre>
<p>We can drive it using <code>times</code> with the <code>x</code> combinator.</p>
<pre><code>joy? 10 [x] times pop
1 2 4 8 16 32 64 128 256 512
</code></pre>
<h2>Generating Multiples of Three and Five</h2>
<p>Look at the treatment of the Project Euler Problem One in the
<a href="/notebooks/Developing_a_Program.html">Developing a Program</a>
notebook and you'll see that we might be interested in generating an endless cycle of:</p>
<pre><code>3 2 1 3 1 2 3
</code></pre>
<p>To do this we want to encode the numbers as pairs of bits in a single integer:</p>
<pre><code>Decimal: 3 2 1 3 1 2 3
Binary: 11 10 01 11 01 10 11
</code></pre>
<p>The number 11100111011011 in binary is 14811 in decimal notation.
We can recover the terms from this number by using <code>4 divmod</code>.</p>
<pre><code>joy? 14811 [4 divmod swap] make-generator
[14811 swap [4 divmod swap] direco]
joy? x
3 [3702 swap [4 divmod swap] direco]
joy? x
3 2 [925 swap [4 divmod swap] direco]
joy? x
3 2 1 [231 swap [4 divmod swap] direco]
joy? x
3 2 1 3 [57 swap [4 divmod swap] direco]
joy? x
3 2 1 3 1 [14 swap [4 divmod swap] direco]
joy? x
3 2 1 3 1 2 [3 swap [4 divmod swap] direco]
joy? x
3 2 1 3 1 2 3 [0 swap [4 divmod swap] direco]
joy? x
3 2 1 3 1 2 3 0 [0 swap [4 divmod swap] direco]
joy? x
3 2 1 3 1 2 3 0 0 [0 swap [4 divmod swap] direco]
joy? x
3 2 1 3 1 2 3 0 0 0 [0 swap [4 divmod swap] direco]
</code></pre>
<p>...we get a generator that works for seven cycles before it reaches zero.</p>
<h3>Reset at Zero</h3>
<p>We need a function that checks if the int has reached zero and resets it if so.
That's easy enough to write:</p>
<pre><code>? [pop 14811] [] branch
</code></pre>
<p>I don't like that we're checking every time even though we know we
only need to reset the integer every seventh time, but this way we
can include this function in the generator (rather than wrapping the
generator in something to do it only every seventh iteration.) So
the "forcing" function is just <code>x</code>.</p>
<h3><code>PE1.1.check</code></h3>
<pre><code>[PE1.1.check ? [pop 14811] [] branch] inscribe
</code></pre>
<h3><code>PE1.1</code></h3>
<pre><code>[PE1.1 4 divmod swap] inscribe
</code></pre>
<p>Now we can <code>make-generator</code>:</p>
<pre><code>joy? 14811 [PE1.1.check PE1.1] make-generator
[14811 swap [PE1.1.check PE1.1] direco]
</code></pre>
<p>We can then "force" the generator with <code>x</code> to get as many terms as we like:</p>
<pre><code>joy? 21 [x] times pop
3 2 1 3 1 2 3 3 2 1 3 1 2 3 3 2 1 3 1 2 3
</code></pre>
<h3>Run 466 times</h3>
<p>In the PE1 problem <a href="/notebooks/Developing_a_Program.html">PE1 problem</a>
we are asked to sum all the multiples of three and five
less than 1000. It's worked out that we need to use our cycle of seven numbers
sixty-six times and then four more.</p>
<pre><code>joy? 7 66 * 4 +
466
</code></pre>
<p>If we drive our generator 466 times and sum the stack we get 999:</p>
<pre><code>joy? 14811 [PE1.1.check PE1.1] make-generator
[14811 swap [PE1.1.check PE1.1] direco]
joy? 466 [x] times pop enstacken sum
999
</code></pre>
<p>If you want to see how this is used read the
<a href="/notebooks/Developing_a_Program.html">Developing a Program</a> notebook.</p>
<h2>A generator for the Fibonacci Sequence.</h2>
<p>Consider:</p>
<pre><code>[b a F] x
[b a F] b a F
</code></pre>
<p>The obvious first thing to do is just add <code>b</code> and <code>a</code>:</p>
<pre><code>[b a F] b a +
[b a F] b+a
</code></pre>
<p>From here we want to arrive at:</p>
<pre><code>b [b+a b F]
</code></pre>
<p>Let's start with <code>swons</code>:</p>
<pre><code>[b a F] b+a swons
[b+a b a F]
</code></pre>
<p>Considering this quote as a stack:</p>
<pre><code>F a b b+a
</code></pre>
<p>We want to get it to:</p>
<pre><code>F b b+a b
</code></pre>
<p>So:</p>
<pre><code>F a b b+a popdd over
F b b+a b
</code></pre>
<p>And therefore:</p>
<pre><code>[b+a b a F] [popdd over] infra
[b b+a b F]
</code></pre>
<p>But we can just use <code>cons</code> to carry <code>b+a</code> into the quote:</p>
<pre><code>[b a F] b+a [popdd over] cons infra
[b a F] [b+a popdd over] infra
[b b+a b F]
</code></pre>
<p>Lastly:</p>
<pre><code>[b b+a b F] uncons
b [b+a b F]
</code></pre>
<p>Putting it all together:</p>
<pre><code>F == + [popdd over] cons infra uncons
fib_gen == [1 1 F]
</code></pre>
<p>Let's call <code>F</code> <code>fib_gen</code>:</p>
<pre><code>[fib_gen + [popdd over] cons infra uncons] inscribe
</code></pre>
<p>We can just write the initial quote and then "force" it with <code>x</code>:</p>
<pre><code>joy? [1 1 fib_gen] 10 [x] times
1 2 3 5 8 13 21 34 55 89 [144 89 fib_gen]
</code></pre>
<p>It skips the first term (1) but if that bothers you you can just prepend it to the program:</p>
<pre><code>1 [1 1 fib_gen] 10 [x] times
</code></pre>
<h2>Project Euler Problem Two</h2>
<blockquote>
<p>By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.</p>
</blockquote>
<p>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 <code>pop</code>s it otherwise.</p>
<p><code>python
define('PE2.1 == dup 2 % [+] [pop] branch')</code></p>
<p>And a predicate function that detects when the terms in the series "exceed four million".</p>
<p><code>python
define('&gt;4M == 4000000 &gt;')</code></p>
<p>Now it's straightforward to define <code>PE2</code> as a recursive function that generates terms in the Fibonacci sequence until they exceed four million and sums the even ones.</p>
<p><code>python
define('PE2 == 0 fib_gen x [pop &gt;4M] [popop] [[PE2.1] dip x] primrec')</code></p>
<p><code>python
J('PE2')</code></p>
<pre><code>4613732
</code></pre>
<p>Here's the collected program definitions:</p>
<pre><code>fib == + swons [popdd over] infra uncons
fib_gen == [1 1 fib]
even == dup 2 %
&gt;4M == 4000000 &gt;
PE2.1 == even [+] [pop] branch
PE2 == 0 fib_gen x [pop &gt;4M] [popop] [[PE2.1] dip x] primrec
</code></pre>
<h3>Even-valued Fibonacci Terms</h3>
<p>Using <code>o</code> for odd and <code>e</code> for even:</p>
<pre><code>o + o = e
e + e = e
o + e = o
</code></pre>
<p>So the Fibonacci sequence considered in terms of just parity would be:</p>
<pre><code>o o e o o e o o e o o e o o e o o e
1 1 2 3 5 8 . . .
</code></pre>
<p>Every third term is even.</p>
<p><code>python
J('[1 0 fib] x x x') # To start the sequence with 1 1 2 3 instead of 1 2 3.</code></p>
<pre><code>1 1 2 [3 2 fib]
</code></pre>
<p>Drive the generator three times and <code>popop</code> the two odd terms.</p>
<p><code>python
J('[1 0 fib] x x x [popop] dipd')</code></p>
<pre><code>2 [3 2 fib]
</code></pre>
<p><code>python
define('PE2.2 == x x x [popop] dipd')</code></p>
<p><code>python
J('[1 0 fib] 10 [PE2.2] times')</code></p>
<pre><code>2 8 34 144 610 2584 10946 46368 196418 832040 [1346269 832040 fib]
</code></pre>
<p>Replace <code>x</code> with our new driver function <code>PE2.2</code> and start our <code>fib</code> generator at <code>1 0</code>.</p>
<p><code>python
J('0 [1 0 fib] PE2.2 [pop &gt;4M] [popop] [[PE2.1] dip PE2.2] primrec')</code></p>
<pre><code>4613732
</code></pre>
<h2>How to compile these?</h2>
<p>You would probably start with a special version of <code>G</code>, and perhaps modifications to the default <code>x</code>?</p>
<h2>An Interesting Variation</h2>
<p><code>python
define('codireco == cons dip rest cons')</code></p>
<p><code>python
V('[0 [dup ++] codireco] x')</code></p>
<pre><code> . [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] .
</code></pre>
<p><code>python
define('G == [codireco] cons cons')</code></p>
<p><code>python
J('230 [dup ++] G 5 [x] times pop')</code></p>
<pre><code>230 231 232 233 234
</code></pre>
</body>
</html>