Function application#

Some operators apply (to) their operands in intricate ways. How do you get a clearer picture of what they actually do? Let’s take outer product ∘.f as an example.

10 20 30∘.×1 2 3 4
10 20 30  40
20 40 60  80
30 60 90 120

Sure, ok, but what actually happened? It may seem simple, but what about:

(3 210×⍳6)∘.×(2 4⍴⍳8)
 10  20  30  40
 50  60  70  80

 20  40  60  80
100 120 140 160


 30  60  90 120
150 180 210 240

 40  80 120 160
200 240 280 320


 50 100 150 200
250 300 350 400

 60 120 180 240
300 360 420 480

What exactly got paired up with what? Here’s a trick you can use to analyse derived functions, that is both functions modified by operators and all tacit functions in general. Let’s replace the function (the operand) with a function which doesn’t actually do the computation, but rather tells us what the computation would be:

10 20 30{'(',,'×',,')'}1 2 3
( 10 20 30 × 1 2 3 )

× is scalar. We can model that too:

10 20 30{{'(',,'×',,')'}¨}1 2 3
┌──────────┬──────────┬──────────┐
│( 10 × 1 )│( 20 × 2 )│( 30 × 3 )│
└──────────┴──────────┴──────────┘
(3 210×⍳6)∘.{{'(',,'×',,')'}¨}(2 4⍴⍳8)
┌────────────┬────────────┬────────────┬────────────┐
│┌──────────┐│┌──────────┐│┌──────────┐│┌──────────┐│
││( 10 × 1 )│││( 10 × 2 )│││( 10 × 3 )│││( 10 × 4 )││
│└──────────┘│└──────────┘│└──────────┘│└──────────┘│
├────────────┼────────────┼────────────┼────────────┤
│┌──────────┐│┌──────────┐│┌──────────┐│┌──────────┐│
││( 10 × 5 )│││( 10 × 6 )│││( 10 × 7 )│││( 10 × 8 )││
│└──────────┘│└──────────┘│└──────────┘│└──────────┘│
└────────────┴────────────┴────────────┴────────────┘
┌────────────┬────────────┬────────────┬────────────┐
│┌──────────┐│┌──────────┐│┌──────────┐│┌──────────┐│
││( 20 × 1 )│││( 20 × 2 )│││( 20 × 3 )│││( 20 × 4 )││
│└──────────┘│└──────────┘│└──────────┘│└──────────┘│
├────────────┼────────────┼────────────┼────────────┤
│┌──────────┐│┌──────────┐│┌──────────┐│┌──────────┐│
││( 20 × 5 )│││( 20 × 6 )│││( 20 × 7 )│││( 20 × 8 )││
│└──────────┘│└──────────┘│└──────────┘│└──────────┘│
└────────────┴────────────┴────────────┴────────────┘

┌────────────┬────────────┬────────────┬────────────┐
│┌──────────┐│┌──────────┐│┌──────────┐│┌──────────┐│
││( 30 × 1 )│││( 30 × 2 )│││( 30 × 3 )│││( 30 × 4 )││
│└──────────┘│└──────────┘│└──────────┘│└──────────┘│
├────────────┼────────────┼────────────┼────────────┤
│┌──────────┐│┌──────────┐│┌──────────┐│┌──────────┐│
││( 30 × 5 )│││( 30 × 6 )│││( 30 × 7 )│││( 30 × 8 )││
│└──────────┘│└──────────┘│└──────────┘│└──────────┘│
└────────────┴────────────┴────────────┴────────────┘
┌────────────┬────────────┬────────────┬────────────┐
│┌──────────┐│┌──────────┐│┌──────────┐│┌──────────┐│
││( 40 × 1 )│││( 40 × 2 )│││( 40 × 3 )│││( 40 × 4 )││
│└──────────┘│└──────────┘│└──────────┘│└──────────┘│
├────────────┼────────────┼────────────┼────────────┤
│┌──────────┐│┌──────────┐│┌──────────┐│┌──────────┐│
││( 40 × 5 )│││( 40 × 6 )│││( 40 × 7 )│││( 40 × 8 )││
│└──────────┘│└──────────┘│└──────────┘│└──────────┘│
└────────────┴────────────┴────────────┴────────────┘

┌────────────┬────────────┬────────────┬────────────┐
│┌──────────┐│┌──────────┐│┌──────────┐│┌──────────┐│
││( 50 × 1 )│││( 50 × 2 )│││( 50 × 3 )│││( 50 × 4 )││
│└──────────┘│└──────────┘│└──────────┘│└──────────┘│
├────────────┼────────────┼────────────┼────────────┤
│┌──────────┐│┌──────────┐│┌──────────┐│┌──────────┐│
││( 50 × 5 )│││( 50 × 6 )│││( 50 × 7 )│││( 50 × 8 )││
│└──────────┘│└──────────┘│└──────────┘│└──────────┘│
└────────────┴────────────┴────────────┴────────────┘
┌────────────┬────────────┬────────────┬────────────┐
│┌──────────┐│┌──────────┐│┌──────────┐│┌──────────┐│
││( 60 × 1 )│││( 60 × 2 )│││( 60 × 3 )│││( 60 × 4 )││
│└──────────┘│└──────────┘│└──────────┘│└──────────┘│
├────────────┼────────────┼────────────┼────────────┤
│┌──────────┐│┌──────────┐│┌──────────┐│┌──────────┐│
││( 60 × 5 )│││( 60 × 6 )│││( 60 × 7 )│││( 60 × 8 )││
│└──────────┘│└──────────┘│└──────────┘│└──────────┘│
└────────────┴────────────┴────────────┴────────────┘

Now we can see what’s going on! Even better if we use indices as arguments:

({'⍺[',(1),';',(2),']'}¨2 3)∘.{{'(',,'×',,')'}¨}({'⍵[',(1),';',(2),']'}¨2 4)
┌─────────────────┬─────────────────┬─────────────────┬─────────────────┐
│┌───────────────┐│┌───────────────┐│┌───────────────┐│┌───────────────┐│
││(⍺[1;1]×⍵[1;1])│││(⍺[1;1]×⍵[1;2])│││(⍺[1;1]×⍵[1;3])│││(⍺[1;1]×⍵[1;4])││
│└───────────────┘│└───────────────┘│└───────────────┘│└───────────────┘│
├─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│┌───────────────┐│┌───────────────┐│┌───────────────┐│┌───────────────┐│
││(⍺[1;1]×⍵[2;1])│││(⍺[1;1]×⍵[2;2])│││(⍺[1;1]×⍵[2;3])│││(⍺[1;1]×⍵[2;4])││
│└───────────────┘│└───────────────┘│└───────────────┘│└───────────────┘│
└─────────────────┴─────────────────┴─────────────────┴─────────────────┘
┌─────────────────┬─────────────────┬─────────────────┬─────────────────┐
│┌───────────────┐│┌───────────────┐│┌───────────────┐│┌───────────────┐│
││(⍺[1;2]×⍵[1;1])│││(⍺[1;2]×⍵[1;2])│││(⍺[1;2]×⍵[1;3])│││(⍺[1;2]×⍵[1;4])││
│└───────────────┘│└───────────────┘│└───────────────┘│└───────────────┘│
├─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│┌───────────────┐│┌───────────────┐│┌───────────────┐│┌───────────────┐│
││(⍺[1;2]×⍵[2;1])│││(⍺[1;2]×⍵[2;2])│││(⍺[1;2]×⍵[2;3])│││(⍺[1;2]×⍵[2;4])││
│└───────────────┘│└───────────────┘│└───────────────┘│└───────────────┘│
└─────────────────┴─────────────────┴─────────────────┴─────────────────┘
┌─────────────────┬─────────────────┬─────────────────┬─────────────────┐
│┌───────────────┐│┌───────────────┐│┌───────────────┐│┌───────────────┐│
││(⍺[1;3]×⍵[1;1])│││(⍺[1;3]×⍵[1;2])│││(⍺[1;3]×⍵[1;3])│││(⍺[1;3]×⍵[1;4])││
│└───────────────┘│└───────────────┘│└───────────────┘│└───────────────┘│
├─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│┌───────────────┐│┌───────────────┐│┌───────────────┐│┌───────────────┐│
││(⍺[1;3]×⍵[2;1])│││(⍺[1;3]×⍵[2;2])│││(⍺[1;3]×⍵[2;3])│││(⍺[1;3]×⍵[2;4])││
│└───────────────┘│└───────────────┘│└───────────────┘│└───────────────┘│
└─────────────────┴─────────────────┴─────────────────┴─────────────────┘

┌─────────────────┬─────────────────┬─────────────────┬─────────────────┐
│┌───────────────┐│┌───────────────┐│┌───────────────┐│┌───────────────┐│
││(⍺[2;1]×⍵[1;1])│││(⍺[2;1]×⍵[1;2])│││(⍺[2;1]×⍵[1;3])│││(⍺[2;1]×⍵[1;4])││
│└───────────────┘│└───────────────┘│└───────────────┘│└───────────────┘│
├─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│┌───────────────┐│┌───────────────┐│┌───────────────┐│┌───────────────┐│
││(⍺[2;1]×⍵[2;1])│││(⍺[2;1]×⍵[2;2])│││(⍺[2;1]×⍵[2;3])│││(⍺[2;1]×⍵[2;4])││
│└───────────────┘│└───────────────┘│└───────────────┘│└───────────────┘│
└─────────────────┴─────────────────┴─────────────────┴─────────────────┘
┌─────────────────┬─────────────────┬─────────────────┬─────────────────┐
│┌───────────────┐│┌───────────────┐│┌───────────────┐│┌───────────────┐│
││(⍺[2;2]×⍵[1;1])│││(⍺[2;2]×⍵[1;2])│││(⍺[2;2]×⍵[1;3])│││(⍺[2;2]×⍵[1;4])││
│└───────────────┘│└───────────────┘│└───────────────┘│└───────────────┘│
├─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│┌───────────────┐│┌───────────────┐│┌───────────────┐│┌───────────────┐│
││(⍺[2;2]×⍵[2;1])│││(⍺[2;2]×⍵[2;2])│││(⍺[2;2]×⍵[2;3])│││(⍺[2;2]×⍵[2;4])││
│└───────────────┘│└───────────────┘│└───────────────┘│└───────────────┘│
└─────────────────┴─────────────────┴─────────────────┴─────────────────┘
┌─────────────────┬─────────────────┬─────────────────┬─────────────────┐
│┌───────────────┐│┌───────────────┐│┌───────────────┐│┌───────────────┐│
││(⍺[2;3]×⍵[1;1])│││(⍺[2;3]×⍵[1;2])│││(⍺[2;3]×⍵[1;3])│││(⍺[2;3]×⍵[1;4])││
│└───────────────┘│└───────────────┘│└───────────────┘│└───────────────┘│
├─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│┌───────────────┐│┌───────────────┐│┌───────────────┐│┌───────────────┐│
││(⍺[2;3]×⍵[2;1])│││(⍺[2;3]×⍵[2;2])│││(⍺[2;3]×⍵[2;3])│││(⍺[2;3]×⍵[2;4])││
│└───────────────┘│└───────────────┘│└───────────────┘│└───────────────┘│
└─────────────────┴─────────────────┴─────────────────┴─────────────────┘

We can make this an “eXplanation” operator:

X{f⍺⍺    '(',,(⎕CR'f'),,')'}

How does it work? First it captures its operand ⍺⍺ as f, then it makes into identity which is a common trick to make ambivalent functions. Finally, it strings together the left arg, the function character representation, and the right arg.

'abc'∘.(×X)'DEF'
┌─────┬─────┬─────┐
│(a×D)│(a×E)│(a×F)│
├─────┼─────┼─────┤
│(b×D)│(b×E)│(b×F)│
├─────┼─────┼─────┤
│(c×D)│(c×E)│(c×F)│
└─────┴─────┴─────┘

OK, now that we have a grip on ∘.f, let’s look at f.g.

'abc'(+X).(×X)'DEF'
┌─────────────────────┐
│((a×D)+((b×E)+(c×F)))│
└─────────────────────┘

The result is enclosed which shows us that if the arguments are vectors (as in this case) then the result is a scalar. What happens with higher-rank arguments?

'abc'(+X).(×X)(3 2'DEFGHI')
┌─────────────────────┬─────────────────────┐
│((a×D)+((b×F)+(c×H)))│((a×E)+((b×G)+(c×I)))│
└─────────────────────┴─────────────────────┘

The left argument was a 3-element vector and the right argument a 3-by-2 matrix. We can see how the left argument cells were distributed to the right argument cells.

(2 3'abcdef')(+X).(×X)3 2'DEFGHI'
┌─────────────────────┬─────────────────────┐
│((a×D)+((b×F)+(c×H)))│((a×E)+((b×G)+(c×I)))│
├─────────────────────┼─────────────────────┤
│((d×D)+((e×F)+(f×H)))│((d×E)+((e×G)+(f×I)))│
└─────────────────────┴─────────────────────┘

OK, now it is getting more interesting. The left arg was 2 3⍴ and the right was 3 2⍴. The result became 2 2⍴. In fact, the rule is that f.g removes the last axis of the left argument and the first axis of the right argument, so the result has the shape (¯1↓⍴⍺),(1↓⍴⍵). So if the left arg is shape 2 4 3 and the right arg is 3 5 1 the result should be shape 2 4 5 1:

(2 4 30)+.×(3 5 10)
2 4 5 1

Let’s return to ∘.f for a moment. What is the rule about the shape of the result of that?

(2 4 30)∘.×(3 5 10)
2 4 3 3 5 1

So the shape of ∘.f is (⍴⍺),(⍴⍵). ∘.f and f.g are definitely related! In fact, Iverson suggested that the slightly anomalous in ∘.f be replaced with a number that indicates how many axes to combine. This way 0.f would be ∘.f. However, there is a more general alternative: the rank operator, . This powerful operator is one many struggle with. Let’s explore it! Let’s use a slightly modified version of X:

X{f⍺⍺  ''  '(',((⎕CR'f')),')'}
(X)2 3 4⎕A
(   ⊂  ABCD )
(      EFGH )
(      IJKL )
(           )
(      MNOP )
(      QRST )
(      UVWX )

This just shows enclosing the rank-3 alphabet.

(X)¯12 3 4⎕A
(   ⊂  ABCD )
(      EFGH )
(      IJKL )

(   ⊂  MNOP )
(      QRST )
(      UVWX )

Let’s begin with negative rank, which is often what you really want. f⍤¯N B applies the function to cells of rank (≢⍴B)-N. So in this case the array had rank 3, and the function was applied to sub-arrays of rank 3-1, that is 2, that is, matrices.

(X)¯22 3 4⎕A
(   ⊂  ABCD )
(   ⊂  EFGH )
(   ⊂  IJKL )

(   ⊂  MNOP )
(   ⊂  QRST )
(   ⊂  UVWX )

Here, the function was applied to sub-arrays of rank 3-2, that is 1, i.e. vectors. Now lets try positive rank.

(X)12 3 4⎕A
(   ⊂  ABCD )
(   ⊂  EFGH )
(   ⊂  IJKL )

(   ⊂  MNOP )
(   ⊂  QRST )
(   ⊂  UVWX )

f⍤N applies the function to sub-arrays of rank N. So f⍤1 digs in until it finds vectors.

(X)22 3 4⎕A
(   ⊂  ABCD )
(      EFGH )
(      IJKL )

(   ⊂  MNOP )
(      QRST )
(      UVWX )

So, too, does ⍤2 apply the function to matrices. What about ⍤0? It applies the function to sub-arrays of rank 0, i.e. scalars. obviously isn’t a useful function on scalars, but some functions are, for example, . Consider the following nested array:

m2 2(2 3⎕A)(3 2⎕A)(2 2⎕A)(3 3⎕A)
┌───┬───┐
│ABC│AB │
│DEF│CD │
│   │EF │
├───┼───┤
│AB │ABC│
│CD │DEF│
│   │GHI│
└───┴───┘

It has four scalars. We can apply on each scalar:

0m
ABCDEF   
ABCDEF   

ABCD     
ABCDEFGHI

Notice the description: on each. In general, ⍤0 is the same as ¨:

¨m
┌──────┬─────────┐
│ABCDEF│ABCDEF   │
├──────┼─────────┤
│ABCD  │ABCDEFGHI│
└──────┴─────────┘

except that “mixes” the results while ¨ encloses them.

↑∊¨m
ABCDEF   
ABCDEF   

ABCD     
ABCDEFGHI
0m
┌──────┬─────────┐
│ABCDEF│ABCDEF   │
├──────┼─────────┤
│ABCD  │ABCDEFGHI│
└──────┴─────────┘

Actually, rank can do more than just that, in a powerful way that ¨ cannot compare to. The derived function can be applied dyadically.

(⎕C 2 3 4⎕A)(,X)12 3 4⎕A
( abcd  ,  ABCD )
( efgh  ,  EFGH )
( ijkl  ,  IJKL )

( mnop  ,  MNOP )
( qrst  ,  QRST )
( uvwx  ,  UVWX )

Here, we’re concatenating the rank-1 sub-arrays of the arguments. Let’s use different ranks for the left and right arguments!

(⎕C 2 2⎕A)(,X)1 22 2 2⎕A
( ab  ,  AB )
(        CD )

( cd  ,  EF )
(        GH )

Here, we are concatenating rank-1 sub-arrays of the left arg with rank-2 sub-arrays of the right arg:

(⎕C 2 2⎕A),1 22 2 2⎕A
aAB
bCD

cEF
dGH

We can express the outer product in terms of rank.

(⎕C 2 2⎕A)∘.(,X)3 2⎕A
┌───────┬───────┐
│(a , A)│(a , B)│
├───────┼───────┤
│(a , C)│(a , D)│
├───────┼───────┤
│(a , E)│(a , F)│
└───────┴───────┘
┌───────┬───────┐
│(b , A)│(b , B)│
├───────┼───────┤
│(b , C)│(b , D)│
├───────┼───────┤
│(b , E)│(b , F)│
└───────┴───────┘

┌───────┬───────┐
│(c , A)│(c , B)│
├───────┼───────┤
│(c , C)│(c , D)│
├───────┼───────┤
│(c , E)│(c , F)│
└───────┴───────┘
┌───────┬───────┐
│(d , A)│(d , B)│
├───────┼───────┤
│(d , C)│(d , D)│
├───────┼───────┤
│(d , E)│(d , F)│
└───────┴───────┘

Note how each scalar in got paired up with the entire . In other words, we need the left rank to be 0 and the right rank to be infinite. But since Dyalog APL only allows arrays of up to rank 15, that is enough (15 = ∞ for very small values of ∞).

⍤N can also take a three-element N. That’s only useful for ambivalent functions. It then means that if the derived function is applied monadically, it gets applied to sub-arrays of rank N[1] and if it is applied dyadically, it is applied to sub-arrays of rank N[2] of and of N[3] of .

(X)1 2 02 2⎕A
(   ⊂  AB )
(   ⊂  CD )

That is, applies to rank-1 sub-arrays.

(⎕C 2 2⎕A)(X)1 2 02 2⎕A
( ab  ⊂ A)
( cd     )

( ab  ⊂ B)
( cd     )


( ab  ⊂ C)
( cd     )

( ab  ⊂ D)
( cd     )

That is, applies to rank-2s of (which happens to be the entire array here) and rank-0s of .

Finally, let’s explore how f∘g works. Let’s again use a slightly modified X:

X{f⍺⍺  ''  '('(⎕CR'f')')'}
(,X)(X)'⍵'  '⍺'(,X)(X)'⍵'
(,(⊂⍵))
(⍺,(⊂⍵))

Here is an example of how we can use this to analyse more complex trains, like this CamelCase splitter:

(⊢⊂⎕A)'CamelCaseRocks'
┌─────┬────┬─────┐
│Camel│Case│Rocks│
└─────┴────┴─────┘

The isn’t necessary, but it is in there for illustration purposes.

(XX⎕A X)'⍵'
((∊∘ABCDEFGHIJKLMNOPQRSTUVWXYZ⍵)⊂(⊢⍵))

So now we can see how works and how is distributed to the outer functions. Here’s an even more complex train, which splits on any number of delimiters:

' ,;'(⊢⊆⍨∘~∊)'some delimiters;in,use'
┌────┬──────────┬──┬───┐
│some│delimiters│in│use│
└────┴──────────┴──┴───┘
'⍺'((X)(X)⍨∘(~X)(X))'⍵'
((~(⍵∊⍺))⊆(⍺⊢⍵))

Now we just have to note the obvious that ⍺⊢⍵ is . This should also explain why and can get you the arguments when in a train.