# The Rank/Atop operator: `⍤`

¶

An algorithm must be seen to be believed. –

Don Knuth

The somewhat startled-looking *Rank* operator, `⍤`

, has a reputation for being one of the harder ones to grasp. It sits on the ‘J’ key, a handy mnemonic, as it was an invention borrowed from the J-language. Well, it makes sense to *me*.

If `⍤`

is given a right operand *function* it becomes *Atop*. We’ll talk about that after we’ve covered the *Rank* version.

Other resources on *Rank* and *Atop*:

Webinars: Rank Operator, Advanced Rank, Rank and Dyadic Transpose

APL Orchard cultivations: Basic Rank, Advanced Rank, Atop and Over

```
⎕IO ← 0
]box on
]rows on
```

In essence, the simple use of *Rank* drills into the argument array and applies its operand to sub-cells of a specified rank only.

Consider the following rank 3 array:

```
]DISPLAY m ← 3 3 3⍴27?27
≢⍴m
```

Let’s say we want to drop the first row of each layer of this array. In this case, we could use a bracket-axis specification to drop:

```
1↓[1]m ⍝ Apply drop ↓ to axis 1, i.e. the y-axis, with ⎕IO←0
```

However, this is now `⎕IO`

dependent, and the bracket-axis spec has some other undesirable properties, including the fact that it cannot be applied to user-defined functions.

The *Rank* operator allows us to solve this in a different way:

```
1↓⍤2⊢m
```

You can think of *Rank* in this case being closer to the problem statement: drop the first major cell of each *sub-cell* that has a rank one less than the argument. Or: in our 3D array, apply the drop to each of its constituent 2D arrays.

Note that *Rank* doesn’t equal *depth*, and this is part of the reason why the *Rank* operator can be confusing. So given a complex vector that contains a variety of scalars and higher-rank arrays, you can’t use the *Rank* operator to selectively apply a function to, say, just the vectors.

```
⎕ ← V ← (1 2 3)(2 2⍴1 1 1 1)(1)
```

```
≢⍤1⊢V ⍝ NOTE: this is wrong!
```

In this case, `V`

is of course already a rank 1 array (i.e. a vector). But wait, there is more confusion to be had! The right operand doesn’t even have to be a scalar at all.

We already saw that if the right operand of `⍤`

is a scalar, we are specifying which rank sub cells we want a function to apply to. For dyadic usage, we instead specify which sub-cells of the left argument should be paired up with which sub-cells of the right argument. It takes a while to wrap your head around this.

Here’s an example:

```
1 2 3,⍤0 1 ⊢ 'hello'
```

This says that the *left* argument of the derived function should be paired at rank 0 (that is each element) with the *right* argument at rank 1 (that is the whole vector in this case). If this feels reminiscent of an outer product, you’re not wrong.

In other words, *Rank pairs up* the arguments of the function it derives. We can examine which elements would be paired up by using the following incantation in place of our function:

```
rankpairs ← ⊂⍤,⍥⊂
```

if you know what that means, you probably don’t need to read this tutorial. If not, don’t worry – treat it as a debug statement:

```
1 2 3 rankpairs⍤0 1 ⊢ 'hello'
```

In the section on array indexing earlier, we introduced the concept of *Sane Indexing* without any further explanation on how it was working. Here it is again, as a tacit function:

```
I←⌷⍨∘⊃⍨⍤0 99 ⍝ Sane indexing, a.k.a select
```

Now that we understand a bit about *Rank*, we can at least make a start in taking that apart. So we have a *Rank* 0 99 to the right, stating that we should combine the left argument at rank 0 (scalars) with the right argument at “rank 99”. Rank 99 means “full rank”, as Dyalog has a max rank of 99. So we should combine “each scalar” to the left with “the whole thing, at whatever rank it happens to be” to the right.

The actual indexing is done by the leading *Squad* (`⌷`

). Let’s de-selfie the expression and make it a dfn. Although we haven’t covered tacit functions yet, we can have the interpreter help us out a bit to see how that expression actually parses:

```
]box on -trains=tree ⍝ Visualise tacit functions as a parse tree
```

```
⌷⍨∘⊃⍨⍤0 99
```

If we separate out the *Rank* expression, after a bit of head-scratching, we arrive at

```
sane←{(⊃⍺)⌷⍵}
```

```
3 sane 'abcdefg'
```

```
1 2 3 sane⍤0 99⊢'abcdefg'
```

Hopefully, we can now see a bit clearer how it works – we simply pass the disclosed left argument vector to *Squad*, and *Rank* does the appropriate combination of left and right: each left element, to the whole (full-rank) of the right.

Here’s another example. Given two integer arrays of the same shape, can we replicate (or compress) the rows of one from the other? We might be excused if we think that we can replicate-reduce, but this doesn’t work:

```
]DISPLAY A ← 3 4⍴2 1 1 1 1 2 1 1 1 1 2 1
]DISPLAY B ← 3 4⍴1 2 3 4 1 2 3 4 1 2 3 4
```

(note: all expressions in the next cell will generate syntax or rank errors)

```
A∘//B ⍝ Nope... SYNTAX ERROR
{A/⍵}/B ⍝ Nope... RANK ERROR
A∘{⍺/⍵}/B ⍝ Nope... SYNTAX ERROR
```

```
SYNTAX ERROR: The function does not take a left argument
A∘//B ⍝ Nope... SYNTAX ERROR
∧
```

```
RANK ERROR
{A/⍵}/B ⍝ Nope... RANK ERROR
∧
```

```
SYNTAX ERROR: The function does not take a left argument
A∘{⍺/⍵}/B ⍝ Nope... SYNTAX ERROR
∧
```

Instead, we need to reach for *Rank*:

```
A/⍤1⊢B
```

If we spell it out in plain words, perhaps the use of *Rank* suggests itself: combine each major cell on the left with the corresponding major cell on the right, and apply the dyadic function “replicate”. Every time you think “combine each major cell on the left…”, you should think *Rank*.

As we’ve seen elsewhere, APL has a somewhat unholy mix of leading and trailing axis operators, mainly for historical reasons. *Rank* allows us to stick to the leading axis versions [Kromberg and Hui]:

Leading axis |
Trailling axis |
Operation |
---|---|---|

(⊖⍤1)⍵ |
⌽⍵ |
reverse |

⍺(⊖⍤1)⍵ |
⍺⌽⍵ |
rotate (scalar ⍺) |

(f⌿⍤1)⍵ |
f/⍵ |
reduce |

(f⍀⍤1)⍵ |
f\⍵ |
scan |

## Rank drills¶

In his paper APL Excercises, Roger Hui sets out the following drills for *Rank*. See if you can predict the results before revealing the answers:

```
,t←2 3 4⍴⍳24
```

```
,⍤3⊢t
]DISPLAY ,⍤2⊢t
]DISPLAY ,⍤1⊢t
```

```
⊂⍤3⊢t
⊂⍤2⊢t
⊂⍤1⊢t
```

```
↑ ,¨ ⊂⍤3⊢t
]DISPLAY ↑ ,¨ ⊂⍤2⊢t
]DISPLAY ↑ ,¨ ⊂⍤1⊢t
```

```
]DISPLAY +⌿⍤3⊢t
]DISPLAY +⌿⍤2⊢t
]DISPLAY +⌿⍤1⊢t
```

```
]DISPLAY ⊖⍤3⊢t
]DISPLAY ⊖⍤2⊢t
]DISPLAY ⊖⍤1⊢t
```

If you got all those right, well done – Roger’s paper has got many more for you to pit your wits against.

## Atop¶

As we mentioned above, from version 18 of Dyalog, `⍤`

can also be *Atop* if given a right operand function instead of array. We’ll see more of different kinds of *Atop* later in the tacit chapter, but for now it’s a functional composition rule, stating that (for the dyadic case),

```
X f⍤g Y → f X g Y
```

It is similar enough to *Jot*, `∘`

, to be confusing. Whilst monadic `∘`

is an atop, dyadic `∘`

is a *beside*. Behold:

```
f∘g X → f(gX) → fgX
f⍤g X → f(gX) → fgX
X f∘g Y → X f g Y
X f⍤g Y → f X g Y
```

Another way to think about it is that in the dyadic case, the difference between `∘`

and `⍤`

is which operand function (`f`

or `g`

) gets the left argument. In the monadic case, *Atop* is equivalent for `∘`

and `⍤`

, so it’s worth using `⍤`

for both monadic and dyadic atops for consistency.

Here is a simple example of a dyadic *Atop*,

```
1 2 3 -⍤+ 4 5 6 ⍝ Negate the sum of two vectors
```

which, following the `X f⍤g Y → f X g Y`

rule, is the same as

```
-1 2 3+4 5 6 ⍝ Negate the sum of two vectors
```

or, as a tacit formulation (which we’ll cover in more detail later),

```
1 2 3 (-+) 4 5 6 ⍝ Negate the sum of two vectors
```