Functions#
A function can applied to data in two ways: dyadically or monadically. That is, a function can be applied to either one or two arguments. Most primitive functions have both a dyadic and a monadic definition. For example -
, when applied dyadically is subtract, and when applied monadically is negate.
10-7 ⍝ dyadic
-5 ⍝ monadic
We distinguish between two types of functions when it comes to applying to data. Scalar functions and mixed functions.
Scalar Functions#
A scalar function will penetrate the arrays that it is applied to, all the way down to the simple scalars.
Examples of scalar functions are the arithmetic functions; +
-
×
÷
etc. A worthy note is that APL uses traditional mathematics notation for multiplication and division: ×
and ÷
. *
is used for exponentiation, which is also scalar.
- 2 1 4 ¯11 6
-(1 2) 3 ((¯2 3 ¯6) (33 11 ¯0.5))
See that the structure of the array remains the same, only the content is changed. The same is of course true for higher rank arrays. If you’re unfamiliar with the concept of rank, you can check out the notebook on arrays.
- 3 2⍴4 2 (1 1)
When a scalar function is applied dyadically, the scalars are paired up between the two arrays and the function is applied between the pairs. If the shapes of the arguments do not match up, an error is reported.
4 2 1×3 7 6
2 3+1 7 7
LENGTH ERROR: Mismatched left and right argument shapes
2 3+1 7 7
∧
(2 4) (6 5 11) 7 + (3 2) (¯3 4 0.1) ¯7
Here the result was (2 4+3 2)(6 5 11+¯3 4 0.1)(7+¯7)
(2 2⍴1 2 3 4)+(2 2⍴5 2 5 1)
The shapes of the arguments need not match up exactly. If one of the arguments is a scalar, you get what is called scalar extension. The scalar is distributed across all the scalars of the other argument.
2×3 4 2
(1 3) (4 5 6) 7*2
(3 3⍴1 2 3 4 5 6 7 8 9)÷9
Additionally scalars inside the arrays can be extended after the items have been paired up.
1 2 3+(3 2 1) (¯1 ¯2 ¯3) ((44 71 11) 1 (32 0.5))
All of the arithmetic functions also have a monadic definition.
-
is negate
÷
is reciprocal
+
is complex-conjugate (Acts as identity for real numbers)
×
returns the unit complex number in the direction of its argument (Acts as signum on real numbers)
*
raises e to the power of its argument
Have a go at testing them all out by modifying the following:
+ 1j5 (0j6 7) 0.4 4j¯1e¯9
Mixed Functions#
Mixed functions apply to larger structures of the argument, for example the rows of an matrix, or just take the entire argument. They can treat each argument differently in this respect.
Monadic ⍴
(shape) is a mixed function. It returns the shape of its argument - the lengths of its dimensions. Obviously this wouldn’t be very useful as a scalar function.
⍴
takes its entire argument into consideration.
⍴ 1 2 3
⍴ (1 2 3)(2 2 2) 4 2
⍴ (3 3⍴0 1 1)
There are many functions in APL and they won’t all be contained in this notebook. ⍴
has been covered in more depth in the arrays notebook, along with monadic ≡
.
To define your own functions, you have three options - dfns, tradfns and tacit functions.
A user defined function behaves the same way as a primitive function. It takes a maximum of two arguments (left and right), and is called either prefix (monadically) or infix (dyadically).
Dfns#
A dfn (dynamic-function) is an APL expression enclosed in braces ({}
) where the left and right arguments are referred to by ⍺
and ⍵
(the leftmost and rightmost characters in the Greek alphabet) respectively. For a monadic function, only ⍵
is used.
times←{⍺×⍵} ⍝ dfns are assigned just like other values
4 times 5
Dfns are anonymous functions (they don’t require a name). This means that they can be used inline like a primitive function.
4 {⍺×⍵} 5
3 {(⍵*2)+(⍺*2)} 4
3 {(⍺,1)⍴⍵} 'APL is fun'
Dfns can be nested as much as you like. ⍺
and ⍵
will always reference the arguments of the dfn defined by the immediately enclosing braces.
3 {{⍵*0.5}(⍵*2)+(⍺*2)} 4
Inside a dfn, you can perform an if a then b else c
check. It takes the form {a:b⋄c}
. a
must evaluate to a boolean singleton, which just means that it is a 1
or 0
whose shape consists of only 1s.
{⍵:'true'⋄'false'} 1
There are many functions in APL that return booleans. Examples are comparisons: <
≤
=
≥
>
The comparisons are all scalar functions.
6>4
1 2 3 4 5=3 2 1 4 5
10 {⍺<⍵:⍺⋄⍵} 12 ⍝ minimum
To perform recursion, you can either use the name of the function or the symbol ∇
which refers to the function it’s in. If it’s not named, you have to use ∇
, which is generally recommended so that you may rename the function without having to change its definition.
fac←{⍵>1:⍵×fac ⍵-1⋄1}
fac 5
{⍵>1:⍵×∇⍵-1⋄1} 5
Tacit functions#
A tacit functions is an APL statement that does not have data on the right.
The simplest tacit expression is a single function:
f←+
1 f 2
Longer tacit functions are often referred to as trains, where each car is either a function or an array. We’ll start by going over monadic trains.
A two-train f g
is called an atop. (f g) X
is equivalent to f (g X)
. f is evaluated atop the result of g.
Since a tacit expression does not have data on the right, to use an unnamed train it must be placed inside parentheses.
(×-)1 0 ¯5
(÷*) 0 3 11
Now, there are two types of three-trains (forks), which are closely related. The f g h
fork and the A g h
fork. Here f
is a function while A
is an array.
(f g h) X
is equivalent to (f X) g (h X)
and (A g h) X
is A g (h X)
(⍴+≡) 4 6⍴1 (2 3) 3 3 ⍝ The shape plus the depth
(1-×)1 0 10 ¯4
Dyadic trains have almost identical definitions to monadic ones.
X (f g) Y ←→ f (X g Y)
3 (×-) 6 ⍝ The sign of the difference
X (f g h) Y ←→ (X f Y) g (X h Y)
4 (×-+) 3 ⍝ The product minus the sum
X (A g h) Y ←→ A g (X h Y)
3 2 0 (2 4 1×+) 11 7 20
Inside trains, it is useful to have the functions ⊢
and ⊣
. ⊢
returns the right argument and ⊣
returns the left. When used monadically they both return the right.
(⊢×1-⊢) 20
3 (⊢⍴⊣) 4 4 ⍝ 4 4⍴3
For any trains longer than three, the rules are still simple. If there are more than three functions, the last three become a three train and are treated as a single function. Then it’s either an atop, a fork, or you have to repeat the previous step.
For example (p q r s) X
becomes (p (q r s)) X
which becomes p ((q r s) X)
which finally evaluates to p ((q X) r (s X))
In the same way X (p q r s t) Y
is equivalent to (X p Y) q ((X r Y) s (X t Y))
2 5 (⊣⍴(1+×)) 4
2 5 (⊣⍴1+×) 4
A particularly common use for trains is when you have a handful of conditions and you want to check whether the input satisfies all of them. This can be easily done using the scalar function ∧
(logical-and). The conditions can be written as cond1 ∧ cond2 ∧ cond3 ∧ cond4...
. Using a train takes a lot of the strain off the reader because the pattern is obvious; all the conditions are being checked on the input and then are anded together. Compare this to {(cond1 ⍵)∧(cond2 ⍵)∧(cond3 ⍵)...}
, where the input has to be explicitly referenced in each condition and the line is cluttered by parentheses.