Update 'stack language recursion' article to new math delimiters

Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
This commit is contained in:
Danila Fedorin 2024-05-13 19:07:01 -07:00
parent 53ff0c39e4
commit 20d8b18a9b

View File

@ -48,22 +48,22 @@ concrete syntax. How about something like:
Let's informally define the meanings of each of the described commands:
1. \\(\\text{Pop} \\; n\\): Removes the top \\(n\\) elements from the stack.
2. \\(\\text{Slide} \\; n \\): Removes the top \\(n\\) elements __after the first element on the stack__.
1. \(\text{Pop} \; n\): Removes the top \(n\) elements from the stack.
2. \(\text{Slide} \; n \): Removes the top \(n\) elements __after the first element on the stack__.
The first element is not removed.
2. \\(\\text{Offset} \\; n \\): Pushes an element from the stack onto the stack, again. When \\(n=0\\),
the top element is pushed, when \\(n=1\\), the second element is pushed, and so on.
3. \\(\\text{Eq}\\): Compares two numbers on top of the stack for equality. The numbers are removed,
2. \(\text{Offset} \; n \): Pushes an element from the stack onto the stack, again. When \(n=0\),
the top element is pushed, when \(n=1\), the second element is pushed, and so on.
3. \(\text{Eq}\): Compares two numbers on top of the stack for equality. The numbers are removed,
and replaced with a boolean indicating whether or not they are equal.
4. \\(\\text{PushI} \\; i \\): Pushes an integer \\(i\\) onto the stack.
5. \\(\\text{Add}\\): Adds two numbers on top of the stack. The two numbers are removed,
4. \(\text{PushI} \; i \): Pushes an integer \(i\) onto the stack.
5. \(\text{Add}\): Adds two numbers on top of the stack. The two numbers are removed,
and replaced with their sum.
6. \\(\\text{Mul}\\): Multiplies two numbers on top of the stack. The two numbers are removed,
6. \(\text{Mul}\): Multiplies two numbers on top of the stack. The two numbers are removed,
and replaced with their product.
7. \\(\\textbf{if}\\)/\\(\\textbf{else}\\): Runs the first list of commands if the boolean "true" is
7. \(\textbf{if}\)/\(\textbf{else}\): Runs the first list of commands if the boolean "true" is
on top of the stack, and the second list of commands if the boolean is "false".
8. \\(\\textbf{func}\\): pushes a function with the given commands onto the stack.
9. \\(\\text{Call}\\): calls the function at the top of the stack. The function is removed,
8. \(\textbf{func}\): pushes a function with the given commands onto the stack.
9. \(\text{Call}\): calls the function at the top of the stack. The function is removed,
and its body is then executed.
Great! Let's now write some dummy programs in our language (and switch to code blocks
@ -106,7 +106,7 @@ of all the computational chaos. We will adopt calling conventions.
When I say calling convention, I mean that every time we call a function, we do it in a
methodical way. There are many possible such methods, but I propose the following:
1. Since \\(\\text{Call}\\) requires that the function you're calling is at the top
1. Since \(\text{Call}\) requires that the function you're calling is at the top
of the stack, we stick with that.
2. If the function expects arguments, we push them on the stack right before the function. The
first argument of the function should be second from the top of the stack (i.e.,
@ -132,7 +132,7 @@ that it is called correctly, of course -- it will receive an integer
on top of the stack. That may not, and likely will not, be the only thing on the stack.
However, to stick by convention 4, we pretend that the stack is empty, and that
trying to manipulate it will result in an error. So, we can start by imagining
an empty stack, with an integer \\(x\\) on top:
an empty stack, with an integer \(x\) on top:
{{< stack >}}
{{< stack_element >}}{{< /stack_element >}}
@ -141,7 +141,7 @@ an empty stack, with an integer \\(x\\) on top:
{{< stack_element >}}\(x\){{< /stack_element >}}
{{< /stack >}}
Then, \\(\\text{PushI} \\; 0\\) will push 0 onto the stack:
Then, \(\text{PushI} \; 0\) will push 0 onto the stack:
{{< stack >}}
{{< stack_element >}}{{< /stack_element >}}
@ -150,7 +150,7 @@ Then, \\(\\text{PushI} \\; 0\\) will push 0 onto the stack:
{{< stack_element >}}\(x\){{< /stack_element >}}
{{< /stack >}}
\\(\\text{Slide} \\; 1\\) will then remove the 1 element after the top element: \\(x\\).
\(\text{Slide} \; 1\) will then remove the 1 element after the top element: \(x\).
We end up with the following stack:
{{< stack >}}
@ -176,14 +176,14 @@ The function must be on top of the stack, as per the semantics of our language
(and, I suppose, convention 1). Because of this, we have to push it last.
It only takes one argument, which we push on the stack first (so that it ends up
below the function, as per convention 2). When both are pushed, we use
\\(\\text{Call}\\) to execute the function, which will proceed as we've seen above.
\(\text{Call}\) to execute the function, which will proceed as we've seen above.
### Get Ahold of Yourself!
How should a function call itself? The fact that functions reside on the stack,
and can therefore be manipulated in the same way as any stack elements. This
opens up an opportunity for us: we can pass the function as an argument
to itself! Then, when it needs to make a recursive call, all it must do
is \\(\\text{Offset}\\) itself onto the top of the stack, then \\(\\text{Call}\\),
is \(\text{Offset}\) itself onto the top of the stack, then \(\text{Call}\),
and voila!
Talk is great, of course, but talking doesn't give us any examples. Let's
@ -191,15 +191,15 @@ walk through an example of writing a recursive function this way. Let's
try [factorial](https://en.wikipedia.org/wiki/Factorial)!
The "easy" implementation of factorial is split into two cases:
the base case, when \\(0! = 1\\) is computed, and the recursive case,
in which we multiply the input number \\(n\\) by the result
of computing factorial for \\(n-1\\). Accordingly, we will use
the \\(\\textbf{if}\\)/\\(\\text{else}\\) command. We will
the base case, when \(0! = 1\) is computed, and the recursive case,
in which we multiply the input number \(n\) by the result
of computing factorial for \(n-1\). Accordingly, we will use
the \(\textbf{if}\)/\(\text{else}\) command. We will
make our function take two arguments, with the number input
as the first ("top") argument, and the function itself as
the second argument. Importantly, we do not want to destroy the input
number by running \\(\\text{Eq}\\) directly on it. Instead,
we first copy it using \\(\\text{Offset} \\; 0\\), then
number by running \(\text{Eq}\) directly on it. Instead,
we first copy it using \(\text{Offset} \; 0\), then
compare it to 0:
```
@ -218,7 +218,7 @@ on the stack:
{{< stack_element >}}factorial{{< /stack_element >}}
{{< /stack >}}
Then, \\(\\text{Offset} \\; 0\\) duplicates the first argument
Then, \(\text{Offset} \; 0\) duplicates the first argument
(the number):
{{< stack >}}
@ -237,7 +237,7 @@ Next, 0 is pushed onto the stack:
{{< stack_element >}}factorial{{< /stack_element >}}
{{< /stack >}}
Finally, \\(\\text{Eq}\\) performs the equality check:
Finally, \(\text{Eq}\) performs the equality check:
{{< stack >}}
{{< stack_element >}}{{< /stack_element >}}
@ -265,7 +265,7 @@ As before, we push the desired answer onto the stack:
{{< stack_element >}}factorial{{< /stack_element >}}
{{< /stack >}}
Then, to follow convention 3, we must get rid of the arguments. We do this by using \\(\\text{Slide}\\):
Then, to follow convention 3, we must get rid of the arguments. We do this by using \(\text{Slide}\):
{{< stack >}}
{{< stack_element >}}{{< /stack_element >}}
@ -274,7 +274,7 @@ Then, to follow convention 3, we must get rid of the arguments. We do this by us
{{< stack_element >}}1{{< /stack_element >}}
{{< /stack >}}
Great! The \\(\\textbf{if}\\) branch is now done, and we're left with the correct answer on the stack.
Great! The \(\textbf{if}\) branch is now done, and we're left with the correct answer on the stack.
Excellent!
It's the recursive case that's more interesting. To make the recursive call, we must carefully
@ -294,7 +294,7 @@ The result is as follows:
{{< stack_element >}}factorial{{< /stack_element >}}
{{< /stack >}}
Next, we must compute \\(n-1\\). This is pretty standard stuff:
Next, we must compute \(n-1\). This is pretty standard stuff:
```
Offset 1
@ -303,7 +303,7 @@ Add
```
Why these three instructions? Well, with the function now on the top of the stack, the number argument is somewhat
buried, and thus, we need to use \\(\\text{Offset} \\; 1\\) to get to it:
buried, and thus, we need to use \(\text{Offset} \; 1\) to get to it:
{{< stack >}}
{{< stack_element >}}\(n\){{< /stack_element >}}
@ -347,7 +347,7 @@ Call
If the function behaves as promised, this will remove the top 3 elements
from the stack. The top element, which is the function itself, will
be removed by the \\(\\text{Call}\\) operator. The two next two elements
be removed by the \(\text{Call}\) operator. The two next two elements
will be removed from the stack and replaced with the result of the function
as per convention 2. The rest of the stack will remain untouched as
per convention 4. We thus expect the stack to look as follows:
@ -368,7 +368,7 @@ Mul
Slide 1
```
The multiplication leaves us with \\(n(n-1)! = n!\\) on top of the stack,
The multiplication leaves us with \(n(n-1)! = n!\) on top of the stack,
and the function argument below it:
{{< stack >}}
@ -378,7 +378,7 @@ and the function argument below it:
{{< stack_element >}}factorial{{< /stack_element >}}
{{< /stack >}}
We then use \\(\\text{Slide}\\) so that only the factorial is on the
We then use \(\text{Slide}\) so that only the factorial is on the
stack, satisfying convention 3:
{{< stack >}}
@ -410,7 +410,7 @@ if {
}
```
We can now invoke this function to compute \\(5!\\) as follows:
We can now invoke this function to compute \(5!\) as follows:
```
func { ... }