User-defined functions
User-defined functions#
In APL, a function can be applied to data, that is, arrays. Note that “arrays” include scalars: a scalar is an array of rank 0.
There are three distinct types of functions, and several ways to create them. The types of functions are tacit, dfns, and tradfns.
Tacit and one-liner dfns can easiest be created by using simple assignment, like we do with arrays:
avg←+⌿÷1⌈≢ ⍝ a tacit function
avg 7 6 2 9 6 3 4 5
5.25
avg←{(+⌿⍵)÷1⌈≢⍵} ⍝ a dfn version of the same function
avg 7 6 2 9 6 3 4 5
5.25
You can’t have multi-line tacit functions, although tacit functions may consist of other multi-line non-tacit functions.
To create a multi-line dfn or tradfn called foo
, the easiest way is to type )ed foo
in the session (the REPL).
The editor will open with the first line pre-populated with the name foo
. You can then start extending the function, e.g. to say (the ]dinput
thing is only required in a Jupyter notebook cell when entering multi-line functions, not in the session):
]dinput
r←foo nums
r←'Here are your numbers: ',⍕nums
Then press Esc to close and save your function in the workspace (the working container – you still need to save your workspace to disk later). The above is a tradfn. A tradfn is good for doing many things, one after another, things that may not necessarily be directly connected. The first line is a header line. It tells APL what the syntax is for that function. In our case, it says that foo
has a result which will be referenced in the code as r
, and it takes a single argument (which must be on the right) called nums
. You can find the full model syntax for the header line here.
Multi-line dfns look like this:
]dinput
foo←{
'Here are your numbers: ',⍕⍵
}
In a dfn, the right argument is always called ⍵
and the result is not named, rather, the first statement which is not an assignment (or after a true guard – we can come back to that) is the result.
If a dfn needs a left argument (all dyadic APL functions are infix) it can be referenced with ⍺
.
Both tradfns and dfns can be made shy. It means that the function by default does not cause implicit display of its result, but the result can still be captured by any code on its left.
A tradfn can be made shy by enclosing its result name in curly braces:
]dinput
{r}←foo nums
r←'Here are your numbers: ',⍕nums
foo 1 2 3 4 ⍝ No output
vals←foo 1 2 3 4 ⍝ Capture return
vals
Here are your numbers: 1 2 3 4
A dfn can be made shy by letting the last statement be after a guard, and have an assignment:
]dinput
foo←{
1:a←'Here are your numbers: ',⍕⍵
}
foo 4 5 6 7 ⍝ No output
vals←foo 4 5 6 7 ⍝ Capture return
vals
Here are your numbers: 4 5 6 7
In most circumstances, you should avoid using shyness, though. It can be confusing.
A guard is a dfn-specific feature. It consists of a statement (a condition) which must evaluate to a Boolean (i.e. 0 or 1), followed by a colon (:) followed by the result of the function if the condition is true.
istrue←{⍵:'true' ⋄ 'false'} ⍝ a guard statement
istrue 1
istrue 0
true
false
⋄
and line breaks are equivalent in almost all cases. One difference is that when you trace through a function, you can only execute one line at a time, even if it has multiple statements separated by ⋄
. Also, one-liner dfns, even if they have ⋄
s, cannot be suspended or traced into – they will always execute completely and quit. If an error happens, the stack is cut back to their caller. This is actually useful, to prevent your program from stopping in a bad state.
Tacit, tradfns and dfns end up being different, even though their outwards behaviour may be identical. They have different detailed name classification. ⎕NC
(Name Classification) is a system function which takes one or more names and tells you something about them:
]dinput
{r}←foo nums ⍝ example tradfn
r←'Here are your numbers: ',⍕nums
avg←+⌿÷1⌈≢ ⍝ example tacit
istrue←{⍵:'true' ⋄ 'false'} ⍝ example dfn
⎕NC 'foo' 'avg' 'istrue'
3.1 3.3 3.2
APL distinguishes between two types of functions when it comes to applying to data: scalar functions and mixed functions.
Scalar functions penetrate the entire structure of the given arrays, all the way until the simple scalars; hence the name. Mixed functions apply to some larger structures, sometimes only regarding one argument, while the other is treated as scalars.
Examples of scalar functions are the arithmetic functions; + - × ÷ ⌈ ⌊
etc. Scalar functions also have something called scalar extension: not only do the functions “pair up” the data, like how 1 2 3+10 20 30
gives 11 22 33
, but they also distribute scalars to all the elements of the other argument, e.g. how 1+10 20 30
gives 11 21 31
.
1 2 3+10 20 30
1+10 20 30
11 22 33
11 21 31
This is useful, because it means you can enclose pieces of your data to tell APL that something should be distributed. This also lets us see the benefit of having both rank and depth.
E.g. ⍺∊⍵
looks if each element of ⍺
is a member of ⍵
.
'hello'∊'CodeGolf'
'hello'∊'Code' 'Golf'
(⊂'hello')∊'Code' 'Golf'
0 1 1 1 1
0 0 0 0 0
0
The first example looks whether each element of 'hello'
is a member of 'CodeGolf'
. The second looks whether each element of 'hello'
is a member of the list of words. Of course, there are no single letters in the list of words. The last example looks whether the word 'hello'
is in the list on the right.
Sometimes, this isn’t enough, though. Sometimes you want to apply your function is a non-standard way. This is where operators come in. APL operators (higher-order functions) take one or two functions as operands and apply them in a specific way.
For example, ¨
(called each) is a monadic operator which applies its operand function to each element of the argument(s). Take, for example, the monadic function ≢
which tallies the length of its argument:
≢'Code' 'Golf'
≢¨'Code' 'Golf'
2
4 4
So while ¨
digs into an array, rank, ⍤
, applies the function to sub-arrays of a specific rank. For example, ≢⍤1
applies Tally to rank 1; that is vectors, thus finding the length of each row (they are of course the same, as all rows in a matrix must be equal length, but you get the idea):
⊢A←2 4⍴'CodeGolf'
(≢⍤1) A
Code Golf
4 4
You can also define your own operators. There are only two types; dops and tradops. There are no tacit operators in APL. Tradops are much like tradfns. The only real difference is the header line. So while a tradfn header can look like result←function arg
, a tradop header can look like result←(fn operator)arg
. This tells APL that operator takes a single function fn
as operand, and the resulting combined function is monadic (takes just the right-argument arg
.
In a dop, much like a dfn, the arguments and operands have fixed names, and the result is the first non-assignment.
The dops’ name of its left (or only) operand is ⍺⍺
and the right operand is ⍵⍵
. The arguments are ⍺
and ⍵
just like in a dfn.
For example, we can create a dop twice
which applies the left argument with the operand two times:
twice←{⍺ ⍺⍺ ⍺ ⍺⍺ ⍵}
2+twice 5
9
Note that this is different than defining plustwice←{⍺+⍺+⍵}
, because the operator can be applied to many different functions, in fact all (dyadic, in our case) functions.
My favourite defined operator is under
:
under←{(⍵⍵⍣¯1) (⍵⍵ ⍺) ⍺⍺ (⍵⍵ ⍵)}
Any guesses as to what it does?
⍣
is another operator, which applies the function on its left as many times as indicated by its right operand.
This also shows that operands may be both functions and arrays, the syntax is the same. ⍺⍺
and ⍵⍵
may each be a function or an array. f⍣¯1
means apply f
negative one time, i.e. apply the inverse of f
. The inverse of ⍟
(log) is *
(power).
Power is to multiplication what multiplication is to addition, so ×under⍟
is power. *under⍟
is tetration.