Add a post about tail-recursive even/odd
This commit is contained in:
parent
30146b68ab
commit
c698499c51
1 changed files with 73 additions and 0 deletions
73
content/posts/tail_recursive_even_odd/note.md
Normal file
73
content/posts/tail_recursive_even_odd/note.md
Normal file
|
@ -0,0 +1,73 @@
|
|||
X-Date: 2024-01-29T21:00:00Z
|
||||
X-Note-Id: c7df8c86-62cb-4032-98d8-6555645af220
|
||||
Subject: Tail-recursive even/odd
|
||||
X-Slug: tail_recursive_even_odd
|
||||
|
||||
Tail calls are a way to save on call stack space. If a function call happens as a last thing a function performs, then
|
||||
it is possible for a language implementation to optimize the call by getting rid of all temporary values on the stack
|
||||
before the call is made.
|
||||
|
||||
Consider this implementation of `even` and `odd` functions in Python:
|
||||
|
||||
```
|
||||
def even(x):
|
||||
if x == 0:
|
||||
return True
|
||||
return odd(x-1)
|
||||
|
||||
def odd(x):
|
||||
if x == 0:
|
||||
return false
|
||||
return even(x-1)
|
||||
```
|
||||
|
||||
Here, the recursive calls are in the "tail" position, and thus in theory it is possible to just discard the caller's
|
||||
stack and proceed with evaluating the callee without making a regular function call. In Python there are no tail calls,
|
||||
but it's not hard to implement in a virtual machine.
|
||||
|
||||
So, this is what I did in my virtual machine. Here's the same example written in the VM assembly:
|
||||
|
||||
```
|
||||
;; Pick a reasonably big number
|
||||
;; to demonstrate that we don't
|
||||
;; use stack much
|
||||
(const val 1000000)
|
||||
(const one 1)
|
||||
(const zero 0)
|
||||
|
||||
(sr 3)
|
||||
(setjump r0 even)
|
||||
(mov r1 val)
|
||||
(call r0 1)
|
||||
|
||||
(aeq r0 one)
|
||||
(retnil)
|
||||
|
||||
;; Even implementation
|
||||
(label even)
|
||||
(sr 3)
|
||||
(jeq r0 zero even-end)
|
||||
|
||||
(setjump r1 odd)
|
||||
(sub r2 r0 one)
|
||||
(tailcall r1 1)
|
||||
|
||||
(label even-end)
|
||||
(ret one)
|
||||
|
||||
;; Odd implementation
|
||||
(label odd)
|
||||
(sr 3)
|
||||
(jeq r0 zero odd-end)
|
||||
|
||||
(setjump r1 even)
|
||||
(sub r2 r0 one)
|
||||
(tailcall r1 1)
|
||||
|
||||
(label odd-end)
|
||||
(ret zero)
|
||||
```
|
||||
|
||||
|
||||
In places where we want to make a tail call, instead of using `call`, you need to use `tailcall`. But otherwise
|
||||
the way you write such calls would be the same.
|
Loading…
Reference in a new issue