Skip to content

A Demonstration of Direct Defn

Vector 4:3, page 95

language design education

Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 95

I-APL is an educational APL

Some of the best APL teaching books such as those by Alvord, Thomson and Iverson use direct definition.

I-APL includes direct definition as well as the “DEL” method for defining functions so every APL textbook will work with I-APL.

A short demonstration of direct definition follows. Please ask questions whenever you like.

To define a function directly you must have it all on one line. The line length is set by ⎕PW and the maximum is 254. The line must begin with the name of the function followed by a colon, thus:

      ODDS:(2|A)/A←⍳2×⍵

Inside the function the right argument is always ⍵ and the left argument is always ⍺. The result of the function is the result of the whole expression after the colon. All variables assigned a value in the expression (such as A in ODDS) are localised. ODDS N returns the first N odd numbers.

      ODDS 20
1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39

ODDS is not origin sensitive.

      ⎕←⎕IO←0
0
      ODDS 20
1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39
      ⎕←⎕IO←1
1

This is the simple form of direct definition. It is very easy to use. Here are some more examples:

      AV:(+/,⍵)÷⍴,⍵

AV returns the average value of the items in its argument.

      AV 1 11 3 9 5 7
6
      AV ⍳99
50

      WHERE: ⍺

WHERE is the same as Iverson’s LEV. It is obviously dyadic but doesn’t use its right argument. By simply returning its left argument it allows us to put an expression on the right of any argument without interfering with it. As the right-hand expression is executed first it can set up conditions to prepare for the left-hand expression. Here is a function called ORIGIN0 (the last character in the name is a zero) to make it easy to test any function in origin zero.

      ORIGIN0: ⍎⍵ WHERE ⎕IO←0
      ORIGIN0 '⍳9'
0 1 2 3 4 5 6 7 8

ASK gives its argument as a prompt and returns whatever is the keyboard response, in this case I keyed "OK"'return'.

      ASK: (⍴P)↓⍞ WHERE ⍞←P←⍵,':'
      ASK 'PROMPT'
PROMPT: OK
OK

Next is the traditional IF in direct form:

      IF: ⍵/⍺

COL is Iverson’s monadic comma-bar “⍪”, which produces a one column table of the items in its argument.

      COL:((⍴,⍵),1)⍴⍵
      COL ⍳5
1
2
3
4
5

This function returns the first N square numbers.

      SQU: +\ODDS ⍵
      SQU 16
1 4 9 16 25 36 49 64 81 100 121 144 169 225 256 324

Triangular numbers are even easier. Notice how easy it is to localise the index origin with WHERE.

      TRI: +\⍳⍵ WHERE ⎕IO←1
      TRI 16
1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136

I owe this FIB function to Joseph de Kerf. You will find it explained in Vector 3.4 on page 120.

      FIB: ⌊.5+(÷R)×(.5×1+R←5*.5)*⍳⍵
      FIB 10
1 1 2 3 5 8 13 21 34 55

Each example so far has consisted of a single expression. Direct definition has a conditional form which contains three expressions. They are separated by colons thus:

      FNNAME:<IF-FALSE-EXPR>:<COND>:<IF-TRUE-EXPR>

The middle expression is evaluated first and should return a single boolean. If its result is true then the IF-TRUE-EXPR gives the result of the function; if its result is false then the IF-FALSE-EXPR does so.

For example if we wanted a function like ASK, but amended so that it will only accept a Y or N and return 0 for N and 1 forY, we could write ASKYN.

      ASKKYN: R : 2=R←'NY'⍳1↑ASK ⍵,'?' WHERE ⎕IO←0 :
ASKYN ⍵ WHERE ⎕←'YES/NO ONLY'

The middle expression sets the index origin to zero, adds a question mark to the prompt and then ASKs it, indexing the first character of the response in 'NY'. If R does equal 2 then the initial character of the response was not found in 'NY' and so the response was not YES or NO and the if-true-expression just displays the additional message 'YES/NO ONLY' and tries again. If R doesn’t equal 2 then it must have been 0 or 1 (the other possible results in origin zero) and as in either case R contains the required result – all the work has been done – the if-false-expression can just be R.

      ASKYN'ARE YOU STILL LYING'
ARE YOU STILL LYING?: UNFAIR
YES/NO ONLY
ARE YOU STILL LYING?: NOT FAIR
0

The example shows the reprompt when the response doesn’t begin with Y or N and also that the test is not really adequate; the second response is not equivalent to NO.

So a definition can use other definitions and also can use itself. Functions which call themselves are recursive. Recursion can be used instead of loops to repeat an algorithm as many times as needed. I find it difficult to construct recursive functions which work the way I want them to. To ensure a recursive function ends properly it should refer to itself only in the first or last expression and the conditional expression should switch to execution of the other when the recursion is to be ended.

I-APL has a dynamic stack and a dynamic symbol table so if you make a mistake and set off an unbounded recursion the whole memory will be filled with recursive calls before it dies with a WS FULL.

Here is a function which builds a table from lines entered at the keyboard.

      MAT: V OVER MAT ⍵ : 0=⍴V←⍞ : 0 0⍴' '

The function OVER simply catenates its left argument over its right. It uses R2 which forces its argument to be a rank two table, MAXC to return the width of the wider table and EXD to extend the tables to the same (maximum) width before catenating them on the first dimension.

      OVER: (C EXD ⍺),[⎕IO]C EXD ⍵ WHERE C←(⍺←R2
⍺)MAXC ⍵←R2 ⍵
      R2: (¯2↑1 1,⍴⍵)⍴⍵      ⍝ FORCES ⍵ TO RANK 2
      MAXC: (1↓⍴⍺)⌈1↓⍴⍵      ⍝ MAX OF COLS IN TWO TABLES
      EXD: ((⍴⍵)⌈0,⍺)↑⍵      ⍝ EXTEND TABLE TO WIDTH ⍺

Here is a use of it. I will enter three names from the keyboard and terminate the function by a ‘return’.

      NAMETABLE←MAT 'A'
HANS-DIETER
GERALD
ANTHONY

      NAMETABLE
HANS-DIETER
GERALD
ANTHONY

I am displeased by the inelegances of MAT. First it requires an argument (I do not see how you can write a recursive niladic function) which has no effect on its result. Second the if-true-expression seems an inelegant waste of time too as it has no effect either; there are only three rows in nametable.

I would be grateful if someone would show me how to do it better and very grateful indeed if someone would teach me how to think these things out so that I could avoid such inelegance without further help.

After that the last two functions I have to show are easy. They are the paradigm of recursion: FACTORIAL and a recursive Fibonacci series generator.

      FACTORIAL: ⍵×FACTORIAL ⍵-1 : ⍵≤1 : 1
      FACTORIAL 9
362880
      FIBS: A,+/¯2↑A←FIBS ⍵-1 : ⍵=2 : 1 1
      FIBS 10
1 1 2 3 5 8 13 21 34 55

Anthony Camacho revised 14 December 1987