Tail Calls
A novel feature of the implementation of dfns is the way in which tail calls are optimised.
When a dfn calls a sub-function, the result of the call may or may not be modified by the calling function before being returned. A call where the result is passed back immediately without modification is termed a tail call.
For example in the following, the first call on function fact
is a tail call because the result of fact
is the result of the whole expression, whereas the second call isn't because the result is subsequently multiplied by ⍵
.
(⍺×⍵)fact ⍵-1 ⍝ Tail call on fact.
⍵×fact ⍵-1 ⍝ Embedded call on fact.
Tail calls occur frequently in dfns, and the interpreter optimises them by re-using the current stack frame instead of creating a new one. This gives a significant saving in both time and workspace usage. It is easy to check whether a call is a tail call by tracing it. An embedded call will pop up a new trace window for the called function, whereas a tail call will re-use the current one.
Using tail calls can improve code performance considerably, although at first the technique might appear obscure. A simple way to think of a tail call is as a branch with arguments. The tail call, in effect, branches to the first line of the function after installing new values for ⍵
and ⍺
.
Iterative algorithms can almost always be coded using tail calls.
In general, when coding a loop, we use the following steps; possibly in a different order depending on whether we want to test at the "top" or the "bottom" of the loop.
- Initialise loop control variable(s).
⍝ init
- Test loop control variable.
⍝ test
- Process body of loop.
⍝ proc
- Modify loop control variable for next iteration.
⍝ mod
- Branch to step 2.
⍝ jump
For example, in classical APL you might find the following:
∇ value←limit loop value ⍝ init
[1] top:→(⎕CT>value-limit)/0 ⍝ test
[2] value←Next value ⍝ proc, mod
[3] →top ⍝ jump
∇
Control structures help us to package these steps:
∇ value←limit loop value ⍝ init
[1] :While ⎕CT<value-limit ⍝ test
[2] value←Next value ⍝ proc, mod
[3] :EndWhile ⍝ jump
∇
Using tail calls:
loop←{⍝ init
⎕CT>⍺-⍵:⍵ ⍝ test
⍺ ∇ Next ⍵ ⍝ proc, mod, jump
}