The At operator: @

Once you understand how to write a program get someone else to write it. –Alan Perlis

Let’s look at the powerful immutable array update operator, At (@). By “immutable”, in this context, we mean non-destructive – it creates a new array, rather than mutating in place. Immutability is a good thing.

The dyadic @ operator has a lot of moving parts, and we won’t cover all possible combinations here.

References for @:

  • APL Cart has loads of examples

  • The APL Cultivations covered it in Lesson 4 (amongst other things)

  • Dyalog docs

Our usual prelude:

⎕IO  0
]box on
]rows on
Was ON
Was OFF

@ can take both functions and arrays as either operand. In its simplest form, the left operand specifies values, and the right operand indices:

0@1 2 31 1 1 1 1 1 1
1 0 0 0 1 1 1

Recall from the introduction to operators that an operator returns a derived function. We inserted a Right tack to make clear the distinction – to stop stranding – between the operator’s right operand and the derived function’s argument. We could equally well have written:

(0@1 2 3) 1 1 1 1 1 1 1
1 0 0 0 1 1 1

We can think of the right operand as defining a selection criteria. For example, it can be a function that when called with the derived function’s argument returns a Boolean array:

'*'@{0 1 1 1 0 0 0 0 0 0 0} 'Hello world' ⍝ Right operand: function returning Boolean array
H***o world

The left operand is either an array providing the new values for every value selected by the right argument, or a function which will be called with each selected element to produce the replacement value. For example, let’s add 5 to every even element:

{5+}@{0=2|} 12
5 1 7 3 9 5 11 7 13 9 15 11

In this case, the right operand is the function {0=2|⍵}, which is called as such:

{0=2|} 12 ⍝ Return Boolean mask indicating the even numbers
1 0 1 0 1 0 1 0 1 0 1 0

The selected numbers are then:

({0=2|} 12)/12 ⍝ Compress
0 2 4 6 8 10

which are passed to the left operand:

{5+} 0 2 4 6 8 10 ⍝ Left operand applied to elements selected by right operand
5 7 9 11 13 15

Finally, @ will make the substitution.

Let’s write our own naive, overly verbose, partial implementation of @ for illustrative purposes.

]dinput
_At_  { ⍝ Partial @ - left and right operands must be functions, and the right arg a vector
    mutator  ⍺⍺                   ⍝ Left operand -- function only
    selector  ⍵⍵                  ⍝ Right operand -- function only
    data                         ⍝ Derived function right argument -- vector only. Note: copy
    mask  selector data
    selection  mask/data          ⍝ Compress the data according to boolean mask
    newvals  mutator selection    ⍝ Mutate our selection
    (mask/data)  newvals          ⍝ Replace with the mutated values in copy
    data                           ⍝ Return result
}
{5+}_At_{0=2|} 12
5 1 7 3 9 5 11 7 13 9 15 11

We can shrink that down a bit without any real loss of clarity:

]dinput
_At_  { ⍝ Partial @ - left and right operands must be functions, and the right arg a vector
    data  
    (mask/data)  ⍺⍺ (mask⍵⍵ )/
    data
}
{5+}_At_{0=2|} 12
5 1 7 3 9 5 11 7 13 9 15 11

We used leading and trailing underscores when naming our operator to indicate visually that it’s a dyad, as suggested by Adám Brudzewsky’s unofficial style guide.

Higher rank: choose and reach

The real @ has many more tricks up its sleeve, of course. It can also be applied to arrays of any rank, not just vectors.

If we look at Dyalog’s specification of @ we have

R{X}(f@g)Y

If g is a simple vector, it chooses major cells in Y. If g is nested, it specifies indices for choose or reach indexing. What does that mean, then? One way to think of it is that the g vector behaves just as if it had been inserted between square brackets.

The first case is straightforward: the major cells of an array are those given by the first axis of the shape. In the case of a 2D matrix, its rows:

-@0 2  3 31 2 3 4 5 6 7 8 9 ⍝ Negate rows 0 and 2: major cells
¯1 ¯2 ¯3 4 5 6 ¯7 ¯8 ¯9

If we want to access individual elements we can use choose indexing – which is a nested vector of indices:

-@(0 0)(2 2)  3 31 2 3 4 5 6 7 8 9 ⍝ Negate corners of main diagonal by choose indexing
¯1 2 3 4 5 6 7 8 ¯9

We can also access elements that are deeply nested using reach indexing, which we met briefly in the indexing section:

  G  2 3('Adam' 1)('Bob' 2)('Carl' 3)('Danni' 4)('Eve' 5)('Frank' 6)
'***' ¯999@((0 0)0)((1 2)1)G
┌─────────┬───────┬─────────┐ │┌────┬─┐ │┌───┬─┐│┌────┬─┐ │ ││Adam│1│ ││Bob│2│││Carl│3│ │ │└────┴─┘ │└───┴─┘│└────┴─┘ │ ├─────────┼───────┼─────────┤ │┌─────┬─┐│┌───┬─┐│┌─────┬─┐│ ││Danni│4│││Eve│5│││Frank│6││ │└─────┴─┘│└───┴─┘│└─────┴─┘│ └─────────┴───────┴─────────┘
┌─────────┬───────┬────────────┐ │┌───┬─┐ │┌───┬─┐│┌────┬─┐ │ ││***│1│ ││Bob│2│││Carl│3│ │ │└───┴─┘ │└───┴─┘│└────┴─┘ │ ├─────────┼───────┼────────────┤ │┌─────┬─┐│┌───┬─┐│┌─────┬────┐│ ││Danni│4│││Eve│5│││Frank│¯999││ │└─────┴─┘│└───┴─┘│└─────┴────┘│ └─────────┴───────┴────────────┘

Examples

Here’s a random collection of handy @ expressions. Many more on APLCart, too.

Replace dashes with spaces

' '@(='-') 'Hello-World-One-Two'
Hello World One Two

Pad an array with zeros

{@(1+⍳⍴)02+⍴} 2 21 1 1 1 
0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0

Array search and replace

]DISPLAY array  ?10 310
]DISPLAY 1 2 3 4 {⍺⍺(⍵⍵⍨∘⊂⍳)@(⍺⍺)} 0 10 100 1000  array 
┌→────┐ ↓3 2 3│ │9 0 4│ │2 1 9│ │9 1 4│ │8 7 9│ │3 1 1│ │5 7 9│ │9 0 9│ │8 9 9│ │8 8 9│ └~────┘
┌→──────────┐ ↓100 10 100│ │ 9 0 1000│ │ 10 0 9│ │ 9 0 1000│ │ 8 7 9│ │100 0 0│ │ 5 7 9│ │ 9 0 9│ │ 8 9 9│ │ 8 8 9│ └~──────────┘

Boolean alternating selection

Here’s a creative use of @ that Adám Brudzewsky posted on APL Orchard.

Given two arrays A, B and a boolean filter C, select items from A where C is false and from B where C is true:

A  1 2 3 4
B  11 22 33 44
C  0 1 0 1
(C/⍥,B)@{C}A
1 22 3 44

If that looks puzzling (we haven’t met the operator yet!), for vectors, it’s just

(C/B)@{C}A
1 22 3 44

All that says is: replace the true spots (as defined by C) in A with the true spots (as defined by C) from B, which we could also do as an assignable indexing expression if we don’t mind mutating A:

(C/A)  C/B
A
1 22 3 44