Function Arrays in APL
The foundations of APL have been be extended to encompass arrays of functions. Nevertheless we have few APLs instruments for handling those arrays: this note is an attempt to open up a new panorama to those willing to cultivate this meagre field.
Building an Array of Functions
It is possible to build an array-of-functions by means of unnamed namespaces.
You can define a set of unnamed namespaces like this:
(ns1 ns2 ns3)←(⎕NS ⍬)(⎕NS ⍬)(⎕NS ⍬)
and build a namespace-array
nsA←ns1,ns2,ns3
nsA is a vector-of-namespaces (shape is 3) and its name class is 2.
Let FOO be a function, for example
FOO←{3+⍵}
and make some assignments:
ns1.f←FOO ⋄ ns2.f←FOO ⋄ ns3.f←FOO
Then nsA.f is a set of functions. Try
nsA.f #. ∇f #. ∇f #. ∇f
nsA.f is a strange object: its name class is 0 ... but
nsA.f 5 8 8 8
and
nsA.⎕NC'f' 3 3 3
Now let us write
FA←nsA.f
and obtain
⎕NC'FA' 3
FA is an actual array-of-functions:
FA 5 8 8 8 FA 5 6 7 8 9 10 FA 5 6 LENGTH ERROR
You can also expunge the involved namespaces and functions; the array-of-functions FA is still alive:
⎕EX 5 3⍴'ns1ns2ns3nsAFOO' FA 5 8 8 8
FA has its own actual identity amongst other APL objects.
Array of Functions for the Parallel Headed APLer
The APL scalar functions have a pervasive behaviour over their arguments and are the most "politically correct" functions in a parallel environment. When a function is not scalar, we can force it to behave in a more diligent manner by means of some operator like primitive each ¨ or saw or perv.
I do not know how you envisage those operators; my basic instinct is to look at them like substitutes for loops.
At the opposite the definition of an array-of-functions carries my mind in a different mood and I actually feel myself thinking in a more holistic way when my attention is forwarded to every kind of arrays.
Consider for example:
⍴¨ (1 2 3)('abcde') 3 5
I usually read (i.e. my internal semantics interpreter reads) that statement in this way: compute the shape of first vector and afterwards of the second one
But if we afford this task ...
nsA←(⎕ns ⍬) (⎕ns ⍬) nsA[1].f←⍴ ⋄ nsA[2].f←⍴ RHO_parallel←nsA.f
... then my mood is much different when I look at:
RHO_parallel (1 2 3)('abcde') 3 5
We can avoid the upper manual task of defining many namespaces and afterwards assigning a function by means of a new operator:
Parallelized←{((⍴⍵)⍴(⎕NS ⍬).##).⍺⍺ ⍵}
and obtain in a more direct manner:
⍴Parallelized (1 2 3)('abcde') ⎕A ⎕A 3 5 26 26
That new operator can be rewritten in a more general way:
Parallelized←{ 0∊⍴⍵:⍵ ⍺←{⍵} ⍝ambivalency ⍺((⍴⍵)⍴(⎕NS ⍬).##).⍺⍺ ⍵ }
It may be the mate of both monadic and dyadic functions, both primitive and defined. Furthermore it modifies the behaviour of primitive each ¨ operator.
I think that the implementation of each operator by Dyalog APL has some drawbacks.
Let us consider the monadic primitive each:
(0⍴⊂⍬)≡⍴¨⍬ 1 (0⍴⊂⍬)≡⍴¨'' 1 (0⍴⊂'')≡⍴¨'' 0
That I cannot understand!
The Dyalog APL language reference says:
If the argument Y is empty, the derived function is applied once to the prototype of Y, and the shape of R is the shape of Y.
The thinking behind Dyalogs implementation of each on null arrays is that the prototypical item of the result is determined by the function, rather than the argument.
Owing to the fact that each is an operator, a more consistent behaviour ought to be:
If the argument Y is empty, the derived function is the prototype of function operand Lop..
The prototype of any function could be the TRANSPARENT function.
You can also consider another example:
{⍵[⍋⍵]} ¨ ⍬ RANK ERROR
On the contrary it should happen like the following:
{⍵[⍋⍵]} Parallelized ⍬
gives the zero length numeric vector.
I like to baptize the Parallelized operator with the name peach, that means parallel each.
peach←Parallelized
Building an Array of Different Functions
We are now going to build an array of possibly different functions.
The procedure is similar to what seen beforehands.
A namespace-array is built:
nsA←⎕NS peach 3⍴⊂⍬ ⍝length 3 vector
and some assignments are made:
nsA[1].f←+ ⋄ nsA[2].f←- ⋄ nsA[3].f←÷ FA←nsA.f
Now the array-of-funtions FA can be exploited:
FA 5 5 ¯5 0.2 FA 3 4 5 3 ¯4 0.2
You can see that there is a major duality tie between the array-of-data and the array-of-functions; APL always tries to behave the parallel way:
- if FA is a (scalar) function and DA is an array-of-data, then FA DA gives an array with the same shape as DA
- if FA is an array-of-functions and DA is a scalar datum, then FA DA gives an array with the same shape as FA
- if FA is an array-of-functions and DA is an array-of-data, then FA and DA must have the same shape and FA DA gives an array with the same shape as FA and DA
In Vector Vol.20 No.1 Graeme D.Robertson illustrated a mathematical application of
a vector-of-functions related to the velocity of a fluid at a point in 3D space;
in traditional notation: V(x,y,z)=(xy, -xy2, yz2)
We define:
fx←{⎕IO←1 ⋄ ⍵[1]×⍵[3]} ⋄ fy←{⎕IO←1 ⋄ -⍵[1]×⍵[2]*2} ⋄ fz←{⎕IO←1 ⋄ ⍵[2]×⍵[3]*2} nsA←⎕NS peach 3⍴⊂⍬ ⍝length 3 namespace-vector nsA[1].f←fx ⋄ nsA[2].f←fy ⋄ nsA[3].f←fz FA←nsA.f
and now can exploit the vector-of-functions FA:
FA 1 2 3 3 ¯4 18
Tools for Handling Arrays of Functions
The Dyalog 10.0 APL interpreter does not allow to handle arrays-of-functions in a direct way.
You cannot use indices:
FA[1]
is a pitfall for the interpreter;
1⊃FA SYNTAX ERROR 4⍴FA SYNTAX ERROR
The structural functions need an effort to be promoted to operator level in order to handle the arrays-of-functions.
But now the arrays-of-functions are a kind of black boxes after they were built.
We may look for some workarounds.
Transformation of a Vector of Functions into a Vector of Namespaces
First of all, let us find a way to obtain an array-of-namespaces from an array-of-functions.
We shall use the FAs canonical representation, which is a class 2 object.
When FA is a vector of functions, its canonical representation is a (complicated) nested array but it is possible extract the built-in functions.
I defined a traditional operator for executing that job; it was not possible to define a function, because its arguments cannot be class 3 objects.
The syntax is
nsArray←(dummyOperand FAtoNS FunctionVector)dummyArg
the result is a namespace-array (class 2) where every namespace contains only one function.
The operator .FAtoNS looks complicated because of the complicated structure of canonical representations inside operators. It could be transformed to a Defined function, because the result is a class 2 object; the version here represented allows to show and comment the logical structure.
∇ nsArray←(fdummy FAtoNS funcArray)dummy;cr;peach;funcName;lasteach;last;true_cr;extract_cr;enlist ⍝ transform a single function or a vector of functions into a namespace array ⍝ funcArray is a function(0 rank vector of functions) or a vector of functions ⍝ nsArray is a namespace-Array(vector) where each ns contains 1 function whose name is f cr←⎕CR'funcArray' :If 2=⍴⍴cr ⍝operand is a single function :OrIf 2≥|≡cr ⍝ or a primitive function nsArray←{ ⍝returns a namespace with function ¨f¨ embedded ns←⎕NS ⍬ ⋄ ns.f←⍎⍵ ⋄ ns }'funcArray' :Else ⍝operand is an array of functions peach←{ ⍝parallel each operator 2≠⎕NC'⍺':((⍴⍵)⍴(⎕NS ⍬).##).⍺⍺ ⍵ ⍺((⍴⍵)⍴(⎕NS ⍬).##).⍺⍺ ⍵ } enlist←{⎕ML←0 ⍝ List ⍺-leaves of nested array. ⍺←0 ⍝ default: list 0-leaves. ⍺≥¯1+|≡⍵:,⍵ ⍝ all shallow leaves: finished. 1↓↑,/(⊂⊂⊃⊃⍵),⍺ ∇¨,⍵ ⍝ otherwise: concatenate sublists. } :If 3∊⍴cr ⍝ housekeeping:⎕CR contains a namespace reference :AndIf '.'≡⊃1↓cr cr←⊃¯1↑cr :EndIf extract_cr←{2>|≡⍵:⍵ ⋄ ∇⊃¯1↑⍵} true_cr←extract_cr peach cr ⍝canon.rep of all functions nsArray←{ ⍝returns a namespace with function ¨f¨ embedded ns funcName←{ ⍝returns function_name and namespace where function was defined ~(,3)≡⍴⍵:⍬(⎕FX' ',⍵) (⊃⍵)(⎕FX' ',⊃¯1↑⍵) }⍵ 0=1↑0⍴funcName:{ns←⎕NS ⍬ ⋄ ns.f←⍎,enlist ⍵ ⋄ ns}⍵ ⍝without canonical representation (primitive function) ns{ ⍝with canonical representation 0∊⍴⍺:{ns←⎕NS ⍬ ⋄ ns.f←⍎,enlist ⍵ ⋄ ns}⍵ ⍺{ns←⎕NS ⍬ ⋄ ns.f←⍺⍎,enlist ⍵ ⋄ ns}⍵ }funcName }peach true_cr :EndIf ∇
Reshaping an Array of Functions
The following operator allows the return of the shape of an array-of-functions.
The syntax is
shape←(dummyOperand FA_shape FunctionArray)dummyArray
the result is a vector (class 2) of the shape of the array-of-functions.
∇ shape←(fdummy FA_shape funcArray)dummy ⍝return shape of an array-of-functions:primitive"monadic ⍴"is promoted to function level-GQR 20040210 ⍝ func is a function(0 rank vector of functions) or a vector of functions ⍝ try: ⍝ ⎕←('' FA_shape {2×⍵})'' ⍝ FA2←(2 3 FA_reshape +)'' ⍝FA2 is an array-of-functions ⍝ ⎕←('' FA_shape FA2})'' shape←⍴(+FAtoNS func)⍬ ⍝ ⎕NC'funcArray'←→3 ⎕NC'shape'←→2 ∇
The following operator allows the reshaping of an array-of-functions.
The syntax is
ArrayFunc←(shape FA_reshape funcArray)dummyArray
the result is an array-of-functions (class 3) obtained by reshaping another array-of-functions (class 3).
∇ ArrayFunc←(shape FA_reshape funcArray)dummy;ns ⍝reshape an array-of-functions: primitive"dyadic ⍴"is promoted to function level-GQR 20040204 ⍝ funcArray is a function(0 rank vector of functions) or a vector of functions ⍝ ArrayFunc is an Array-of-Functions ⍝ try: ⍝ FA2←(2 3 FA_reshape +)0 ⍝FA2 is an array-of-functions ⍝ FA2←(4 FA_reshape {2×⍵})0 ⍝FA2 is a vector-of-functions ⍝ FA3←(1 3 FA_reshape FA2)0 ⍝FA3 is an array-of-functions ns←(+FAtoNS funcArray)⍬ ⍝the function_array becomes a namespace_array;⎕NC'ns'←→2 ArrayFunc←(shape⍴ns).f ⍝ ⎕NC'funcArray'←→3 ⎕NC'ArrayFunc'←→3 ∇
Indexing an Array of Functions
The following operator allows the return of a sub-Array from an array-of-functions.
The syntax is
ArrayFunc←(indicesArray FA_from funcArray)dummyArray
the result is an array-of-functions (class 3) obtained by means of another array-of-functions (class 3).
For seek of simplicity let us impose that indicesArray is a vector with same length as the shape of funcArray operand.
∇ ArrayFunc←(indicesArray FA_from funcArray)dummyArray;ns ⍝indexing of an array-of-functions: primitive "[ ]"is promoted to function level ⍝ funcArray is a function(0 rank vector of functions) or a vector of functions ⍝ ArrayFunc is an Array-of-Functions ⍝ try: ⍝ FA2←(2 4 FA_reshape {2×⍵})0 ⍝FA2 is a (2×4) array-of-functions ⍝ FA3←(1 1 FA_from FA2)0 ⍝FA3 is a (scalar) array-of-functions ⍝ FA4←((1 2) (2 3)FA_from FA2)0 ⍝FA4 is a (2×2) array-of-functions ns←(+FAtoNS funcArray)⍬ ⍝the function_array becomes a namespace_array;⎕NC'ns'←→2 :Select ⍴⍴ns :Case ,0 ⋄ ∘ ⍝length error :Case ,1 ⋄ ArrayFunc←ns[indicesArray].f :Case ,2 ⋄ ArrayFunc←ns[⊃1↑indicesArray;⊃¯1↑indicesArray].f :Case ,3 ⋄ ∘ ⍝ et coetera ⍝ et coetera :EndSelect ⍝ ⎕NC'funcArray'←→3 ⎕NC'ArrayFunc'←→3 ∇
Catenating two vectors of functions
By means of the last structural operator we can have arrays-of-functions whith different function-items.
The syntax is
ArrayFunc←(LfuncVector FA_catenate RfuncVector)dummyArray
the result is a new vector-of-functions (class 3) obtained by means of two vector-of-functions (class 3).
∇ ArrayFunc←(Lfunc FA_catenate Rfunc)dummyArray;Lns;Rns ⍝ catenate 2 vectors of functions: primitive "," is promoted to function level-GQR 20040204 ⍝ Lfunc is a function(0 rank vector of functions) or a vector of functions;same for Rfunc ⍝ ArrayFunc is an Array(vector) of Functions whose names are Lfunc and Rfunc ⍝ try: ⍝ FA←(+ FA_catenate -)0 ⍝FA is a vector of functions : FA 3 ←→ 3 ¯3 ⍝ FA←({2×⍵} FA_catenate -)0 ⍝FA is a vector of functions : FA 3 ←→ 6 ¯3 ⍝ FB←(× FA_catenate FA)0 ⍝FB is a vector of functions : FB 3 ←→ 1 6 3 Lns←(''FAtoNS Lfunc)⍬ ⍝the left function_array becomes a namespace_array;⎕NC'Lns'←→2 Rns←(''FAtoNS Rfunc)⍬ ⍝the right function_array becomes a namespace_array;⎕NC'Rns'←→2 ArrayFunc←(Lns,Rns).f ⍝ ⎕NC'Lfunc'←→3 ⎕NC'Rfunc'←→3 ⎕NC'ArrayFunc'←→3 ∇
Some Examples
⎕←(''FA_shape +)'' ⍝the shape of a primitive function is ⍬ ⎕←(''FA_shape {,⍵})'' ⍝the shape of a function is ⍬ FA←(+ FA_catenate -)0 ⍝FA is a vector of functions : FA 3 ←→ 3 ¯3 FA←({2×⍵} FA_catenate -)0 ⍝FA is a vector of functions : FA 3 ←→ 6 ¯3 ⎕←(''FA_shape FA)'' 2 FA2←(2 4 FA_reshape FA)0 ⍝FA2 is a (2×4) array-of-functions ⎕←(''FA_shape FA2)'' 2 4 FB←(× FA_catenate FA)0 ⍝FB is a vector of functions : FB 3 ←→ 1 6 3 FB2←(2 6 FA_reshape FB)0 ⍝FA2 is a (2×4) array-of-functions FB3←((1 2) (2 3 4) FA_from FB2)0 ⍝FB3 is an array-of-functions
References
[1] G.D.Robertson, New Foundations, Vector 20.1(2003) 132-142
[2] Dyalog APL/W version 10.0 Language Reference(2003)