Anonymous Functions/Operators/Hyperators
Anonymous functions and operators (AFOs) are a one-line grouping of one or more statements all enclosed in braces such as {(+⌿⍵)÷≢⍵}. This syntax is useful for one-line functions and operators to complement the existing definition types of user-defined: ∇ Z←avg R, trains: (+⌿ ÷ ≢), and derived: ,∘⍋∘⍋∘,.
These objects may be named as in avg←{(+⌿⍵)÷≢⍵}, and named or unnamed may be used in place of any APL function (primitive, operator operand, derived, train, user-defined) including within another AFO.
Normally, the statements within the braces execute one by one from left to right, just as in the Execute primitive. Execution of the AFO terminates on the first occurrence of one of the following:
Function Arguments
To define an anonymous function, use ⍺ as the (optional) left argument, ∇ as the name of the anonymous function (for recursion), and ⍵ as the name of the right argument. For example,
3 5{√+/⍺ ⍵*2}4 12 ⍝ Pythagorean theorem
5 13
{s←(+/⍵)÷2 ⋄ √×/s-0,⍵}3 4 5 ⍝ Heron's formula for triangle area
6
Operator Operands
To define an anonymous operator, use the above special names along with ⍺⍺ as the name of the left operand, ∇∇ as the name of the operator (for recursion), and ⍵⍵ as the name of the right operand. If neither ⍺⍺ nor ⍵⍵ appears as a token between the braces and outside of character constants, then the object is a function, not an operator. For example,
f←{∘.⍺⍺⍨⍳⍵} |
||||||
=f 4 |
⌈f 4 |
*f 4 |
≤f 4 |
Ambivalent AFOs
User-defined functions/operators allow you to specify in their headers that the left argument is optional by enclosing it in braces, as in ∇ Z←{L} foo R, and to test for the presence or absence of the optional argument using 0=⎕NC 'L'.
This behavior is also available to AFOs by including a statement that assigns a value to ⍺. If ⍺ does not have a value when that statement is encountered, the statement is executed; otherwise, that entire statement is ignored including any side effects. This behavior obviates the need to use 0=⎕NC '⍺' to test for a value in ⍺. It also means that as a consequence of this rule, regardless of how the AFO is called, any second or subsequent statements that assign a value to ⍺ are always ignored.
For example,
f←{⍺←2 ⋄ ⍺√⍵}
f 16
4
3 f 27
3
f←{⎕←⍺←⍵ ⋄ (⍳⍺)∘.⍺⍺⍳⍵}
⌊f 4
4
1 1 1 1
1 2 2 2
1 2 3 3
1 2 3 4
3⌊f 4
1 1 1 1
1 2 2 2
1 2 3 3
f←{⎕←⍺←9 ⋄ ⎕←⍺←0 ⋄ ⍺+⍵}
4 f 12 ⍝ Both assignments of ⍺ ignored
16
f 12 ⍝ Second assignment of ⍺ ignored
9
21
Valences
An AFO may be
- Ambivalent (must be called with either one or two arguments),
- Dyadic (must be called with two arguments),
- Monadic (must be called with one argument), or
- Niladic (must be called with no arguments),
depending upon which of the special symbols are present in its definition.
Disregarding special symbols inside of character constants
- For anonymous functions (and operators):
- If ⍺← appears as a sequence of tokens, then the (derived) function is ambivalent,
- Otherwise, if ⍺ appears as a token, the (derived) function is dyadic,
- Otherwise, if ⍵ appears as a token, the (derived) function is monadic,
- Otherwise, if neither ⍺ nor ⍵ appears as a token, the (derived) function is niladic.
- For anonymous operators:
- If ⍵⍵ appears as a token, the operator is dyadic (must be called with two operands — left and right),
- Otherwise, if ⍺⍺ appears as a token, the operator is monadic (must be called with one operand — left only).
For the moment, the case of a niladic derived function from either a monadic or dyadic operator signals a SYNTAX ERROR.
For example,
{⍵}2
2
1{⍵}2
VALENCE ERROR
1{⍵}2
^
1{⍺+⍵}2
3
{⍺+⍵}2
VALENCE ERROR
{⍺+⍵}2
∧
{⍺←2 ⋄ ⍺+⍵}2 ⍝ Ambivalent function, monadic with default left argument 2
4
3{⍺←2 ⋄ ⍺+⍵}2 ⍝ Ambivalent function, dyadic with left argument 3
5
{⍳3} ⍝ A three-element simple vector
1 2 3
{⍳3}23 ⍝ A two-element nested vector
1 2 3 23
12{⍳3}23 ⍝ A three-element nested vector
12 1 2 3 23
3{∘.=⍨⍳⍺⍺} ⍝ A niladic derived function from a monadic operator
SYNTAX ERROR
3{∘.=⍨⍳⍺⍺}
∧
Guards
Guards are used to test for a condition and execute a statement or not depending upon the value of the conditional expression. This behavior is identical to the control structure
:if Cond ⋄ CondStmt ⋄ :return ⋄ :end
Guards appear as a Boolean-valued APL expression (Cond) followed by a colon, followed by a single APL statement (CondStmt) as in
{... ⋄ Cond:CondStmt ⋄ ....}
which executes CondStmt and terminates the AFO if and only if Cond evaluates to TRUE (1).
For example,
{... ⋄ 0∊⍴⍵:'empty right argument' ⋄ ....}
As many guard statements may appear in an AFO as desired. They are executed in turn until one of them evaluates to TRUE, at which time the statement following the colon is executed and the function terminates with any result of that conditional statement as the result of the AFO.
If Cond does not evaluate to a Boolean-valued scalar or one-element vector, a RANK, LENGTH, or DOMAIN ERROR is signalled, as appropriate.
If the rightmost statement is executed and it is a guard statement whose Cond evaluates to FALSE (0), the AFO terminates with no value; if the result of that AFO is assigned, a VALUE ERROR is signalled.
Shy Results
A "shy" result is any result that doesn't automatically display its value, a simple example of which is L←⍳3: the result is ⍳3 (as evidenced by the extension to 3+L←⍳3), but because it has been assigned to a name, the result doesn't automatically display. Other ways to produce a shy result are to use the Sink syntax ←⍳3, or to call a user-defined function that declares in its header that the result is shy as in ∇ {Z}←foo R.
A shy result propagates up the chain of results just as it does through the Execute primitive. That is, if the last statement executed has a shy result, the result of Execute is shy, too.
Typically, ⎕← is used to expose a shy result.
AFOs also may have shy results by virtue of the final result being
- Sinked,
- Assigned to a name, or
- From a shy user-defined function.
For example,
{←⍳3}
⎕←{←⍳3}
1 2 3
{L←⍳3}
⎕←{L←⍳3}
1 2 3
Ordinarily, a shy result doesn't terminate the AFO, as in
{←⍳3 ⋄ 'abc'}
abc
To force termination, precede the assignment with a guard as in
{⍵=0:←'Zero' ⋄ ⍵>0:←'Positive' ⋄ ←'Negative'} R
which terminates with a shy result in all three cases.
Localization
Unlike user-defined functions, in AFOs all names to which an assignment is made are automatically localized. As a result, a direct assignment inside an AFO cannot affect the value of a name defined higher up in the execution chain unless it is made via a user-defined function or the Execute primitive as in
L←⍳9 ⋄ {L←"abc" ⋄ ⍵}23
23
L
1 2 3 4 5 6 7 8 9
L←⍳9 ⋄ {⍎'L←"abc"' ⋄ ⍵}23
23
L
abc
Scoping
When calls to user-defined functions are nested, a reference to a name by an inner function is resolved by looking up the chain of nested functions for the nearest level at which that name is localized. Looked at from a different perspective, when a name is localized to a function, its scope consists of all functions called by that function. This is called dynamic scoping.
AFOs use a different mechanism where names referenced by an AFO are resolved by the context in which the AFO is defined, not where it is used. This is called lexical scoping or static scoping.
In other words, to paraphrase Wikipedia, "This means that if function f invokes function g that is defined outside the context of f, then under lexical scoping, function g does not have access to f's local variables (since the definition of g is not inside the definition of f), while under dynamic scoping, function g does have access to f's local variables (since the invocation of g is inside the invocation of f)".
For example,
∇ foo;f;g;a
[1] a←'static'
[2] ←⎕fx 'f R;a' 'a←"dynamic"' 'g R'
[3] ←⎕fx 'g R' '"scope ←→ ",a'
[4] f 'a'
∇
foo
scope ←→ dynamic
{a←'static' ⋄ f←{a←'dynamic' ⋄ g ⍵} ⋄ g←{'scope ←→ ',a ⋄ ⍵} ⋄ f 'a'}
scope ←→ static
Recursion
AFOs may be called recursively, but because they might be unnamed (i.e., anonymous), the ∇ symbol is used to name the AFO.
For example, with anonymous functions,
⍝ Fibonacci sequence algorithm
f←{⍺←10 ⋄ ⍺=1:⍵ ⋄ (⍺-1)∇⍵,+/¯2↑⍵}
f 1
1 1 2 3 5 8 13 21 34 55
20 f 1
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
⍝ Euclidean algorithm for Greatest Common Divisor
12 {⍺=0:|⍵ ⋄ (⍵|⍺)∇⍺} 8
4
With anonymous operators, ∇ references the derived function with its operand(s) already bound, and ∇∇ references the operator itself with its operand(s) yet to be bound. That is, for monadic anonymous operators ∇ ←→ ⍺⍺∇∇, and for dyadic anonymous operators ∇ ←→ ⍺⍺∇∇⍵⍵.
For example, with anonymous operators,
⍝ Generalized definition of vector reduction
f←{0=⍴⍵:⍺⍺/0⍴⍵ ⋄ 1=⍴⍵:⊂↑⍵ ⋄ ⊂(↑⍵)⍺⍺ ⊃∇1↓⍵}
+f⍳4
10
×f⍳4
24
+f(1 2)(3 4)
4 6
A more advanced example of recursion of an anonymous operator is hyperoperation expressed below as a monadic operator where its operand N indexes successively more powerful functions as derived functions:
- N=0 is the successor function (i.e., adds 1 to ⍵),
- N=1 is the addition function of ⍺ to ⍵, i.e. the successor function on ⍺ repeated ⍵ times — ⍺+⍵,
- N=2 is the multiplication function of ⍺ by ⍵, i.e. the addition function on ⍺ repeated ⍵ times — +/⍵⍴a ←→ ⍺×⍵,
- N=3 is the exponentiation function of ⍺ to the power ⍵, i.e. the multiplication function on ⍺ repeated ⍵ times — ×/⍵⍴a ←→ ⍺*⍵,
- N=4 is the tetration function (related to the Ackermann-Péter function), i.e. the exponentiation function on ⍺ repeated ⍵ times — */⍵⍴⍺,
- N=5 is the pentation function, i.e. the tetration function on ⍺ repeated ⍵ times,
- etc.
written on multiple lines for clarity:
ho←{⍺⍺=0 :⍵+1
⋄ (⍺⍺=1)∧⍵=0:⍺
⋄ (⍺⍺=2)∧⍵=0:0
⋄ (⍺⍺≥3)∧⍵=0:1
⋄ ⍺ ((⍺⍺-1)∇∇) ⍺∇⍵-1}
2 (0 ho) 5 ⍝ Successor: 1+5 (left argument ignored)
6
2 (1 ho) 5 ⍝ Addition: 2+5
7
2 (2 ho) 5 ⍝ Multiplication: 2×5
10
2 (3 ho) 5 ⍝ Exponentiation: 2*5
32
If we could compute 2x (4 ho) 5x in a reasonable amount of time, it would return a number with 19,729 digits (= ⍴⍕*/5⍴2x)!
N.B.: Both of the ∇ and ∇∇ names may be used in user-defined functions, where they have the same meaning as they do in AFOs.
Restrictions
- For the moment, AFOs may be written on one line only.
- As a consequence of the above one-line restriction, AFOs may not contain comments.
- Assignments to any of the special names (∇, ⍵, ⍺⍺, ∇∇, ⍵⍵) except for ⍺ are not allowed; they signal a SYNTAX ERROR.
- Assignments to ⍺ within a guard statement are not allowed; they signal a SYNTAX ERROR.
- None of the special names may be erased, via ⎕EX or otherwise.
- Goto statements are not allowed; they signal a SYNTAX ERROR.
- Control structures (e.g., :if ... ⋄ ... ⋄ :end) are not allowed; they signal a SYNTAX ERROR.
Acknowledgements
This design of this feature was copied (with minor changes) from the same feature designed and implemented by the folks at Dyalog, Inc.