Replicate
/ ⌿
|
Replicate (/, ⌿), or Copy (#) in J, is a dyadic function or monadic operator that copies each element of the right argument a given number of times, ordering the copies along a specified axis. Typically / is called Replicate while ⌿ is called "Replicate First" or an equivalent. Replicate is a widely-accepted extension of the function Compress, which requires the number of copies to be Boolean: each element is either retained (1 copy) or discarded (0 copies). Replicate with a Boolean left argument or operand may still be called "Compress".
Replicate is usually associated with Expand (\), and the two functions are related to Mask and Mesh. It is also closely related to the Indices function. It shares a glyph with Reduce even though Replicate is naturally a function and Reduce must be an operator. This incongruity is sometimes resolved by making Replicate an operator itself, and sometimes by function-operator overloading allowing both syntactic elements to coexist.
Outside of APL, filter typically provides the functionality of Compress, while Replicate has no common equivalent.
Examples
When used with a Boolean array (often called a "mask") on the left, Replicate is called Compress. It filters the right argument, returning only those elements which correspond to 1s in the provided mask.
1 1 0 1 0 1 0 0 / 'compress' cope
If the right argument is an array of indices generated by Iota, Replicate resembles the function Indices.
1 1 0 0 1 / ⍳5 1 2 5
With an array of non-negative integers, Replicate copies each element of the right argument the corresponding number of times. As with Compress, these copies retain their original ordering, and the length of the result is the sum of the control array.
0 3 0 0 2 0 1 0 2 / 'replicate'
eeeiiaee
+/ 0 3 0 0 2 0 1 0 2
8
⍴ 0 3 0 0 2 0 1 0 2 / 'replicate'
8
Replicate usually allows scalar extension of the left argument, which results in every element being copied a fixed number of times.
3 / 'replicate' rrreeepppllliiicccaaattteee
Negative numbers
An extension introduced by NARS allows either positive or negative integers, where a negative number indicates that a fill element should be used instead of an element from the right argument. In this case the argument lengths must be equal (unless one side is a singleton). APL2 defined a different extension: negative numbers do not correspond to any element of the right argument, but still indicate that many fills should be inserted. In the APL2 extension the length of the right argument is the number of non-negative elements in the left argument. In both extensions the length of the result is the sum of the absolute value of the control array.
0 2 ¯3 1 / ⍳4 2 2 0 0 0 4
0 2 ¯3 1 / ⍳3 2 2 0 0 0 3
The extensions are the same when the right argument is subject to singleton extension. This extension was usually supported before any extension to negative numbers, but would not typically be useful because v/s (+/v)/s where v is a non-negative integer vector and s is a singleton.
1 ¯2 3 / 'a' a aaa
High-rank arrays
Replicate works along a particular axis, which can be specified in languages with function axis and otherwise is the first axis for ⌿, and the last axis for / (except in A+, which uses / for the first-axis form and has no last-axis form).
⎕←A ← 4 6⍴⎕A
ABCDEF
GHIJKL
MNOPQR
STUVWX
1 0 0 4 0 2 / A
ADDDDFF
GJJJJLL
MPPPPRR
SVVVVXX
0 2 1 1 ⌿ A
GHIJKL
GHIJKL
MNOPQR
STUVWX
APL2 further extends the singleton extension of the right argument, allowing it to have length 1 along the replication axis even if other axes have lengths not equal to 1.
1 ¯2 3 / ⍪'abc' a aaa b bbb c ccc
dzaima/APL expects arguments of ⌿ to have matching shape, and replicates the ravel of both.
Operator or function?
- Main article: Function-operator overloading
The syntax a / b is ambiguous: it may be an invocation of a dyadic function / with left argument a and right argument b, or of a monadic operator with operand a and right argument b. In early APLs there was no way to resolve this ambiguity, but with the extension of operators to allow arbitrary function operands instead of a specified set of primitive functions, the distinction becomes apparent: a function Replicate can be used as an operand while an operator Replicate cannot.
One test of Replicate's nature is to try Replicate Each[1] with an expression such as 1 3 /¨ 'ab' 'cd'. If Replicate is implemented as an operator, it will be applied to the operand 1 3, and Each will be applied to the resulting derived function 1 3/.
1 3 /¨ 'ab' 'cd'
abbb cddd
(1 3/)¨ 'ab' 'cd'
abbb cddd
If Replicate is a function, then Each will apply to Replicate only, and the resulting derived function will be invoked monadically.
1 3 /¨ 'ab' 'cd'
ab cccddd
1 3 (/¨) 'ab' 'cd'
ab cccddd
In early APLs such as APL\360, applying an operator to Compress will always result in a SYNTAX ERROR, because Compress is not an allowed operand of any operator. This is also the case in ngn/apl: although operators can apply to any function, Replicate cannot be used unless both arguments are immediately available. In both cases there is no way to determine whether Replicate "acts like a function" or "acts like an operator".
History
Compress was described in A Programming Language, where it was written with the symbols and . In Iverson notation compression was particularly important because Take and Drop could be performed only by compression with a prefix or suffix vector. It was included in APL\360, which changed the doubled slash to a barred slash ⌿, and allowed a specified axis and singleton extension on both sides (very briefly, singleton extension was allowed only for the right argument[2]). The APL\360 definition continued to be included in APLs unchanged until 1980.
In 1980, Bob Bernecky introduced the extension Replicate to SHARP APL: he allowed an operand (since SHARP's Replicate is an operator) consisting of non-negative integers rather than just Booleans to indicate the number of times to copy.[3][4] This extension was rapidly and widely adopted, starting with NARS in 1981, and is now a feature of the ISO/IEC 13751:2001 standard.
Two extensions to allow negative numbers in the left argument have been introduced, in each case specifying that the negative of a number indicates that many fill elements should appear in the result. In 1981 NARS specified that these fill elements replace the corresponding right argument element, so that the lengths of the left and right arguments are always equal, and extended Expand similarly. APL2, in 1984, made the opposite choice, so that the length of the right argument along the specified axis is equal to the number of non-negative elements on the left. APL2 also loosened the conformability requirements further than simply allowing singleton extension: it allowed a right argument with length 1 along the replication axis to be extended. Dyalog APL, created before APL2, adopted the NARS definition for negative elements but added APL2 conformability extension in version 13.1. Later APLX took advantage of the fact that the two negative number extensions can be distinguished by the length of the left argument, and implemented every NARS and APL2 extension.
A+ and J modified Replicate to fit leading axis theory. Rather than allow Replicate to operate on any axis they have only one Replicate function (in A+, /; in J, #) which works on the first axis—it copies major cells rather than elements. Both languages rejected the NARS extension to negative left arguments, but J introduced its own system to add fill elements by allowing complex numbers in the left argument, and removed the Expand function entirely. Arthur Whitney went on to make a more radical change in K, removing Replicate entirely in favor of Where.
Extension support
Here ">1" refers to the SHARP APL extension to non-negative integers, while "<0" refers to extension to negative integers in either NARS or APL2 style. Conformability refers to extension of the right argument only, as all languages allow scalar extension of the left argument.
| Language | Type | >1 | <0 | Conformability extension |
Axis specification |
Notes | |
|---|---|---|---|---|---|---|---|
| NARS | APL2 | ||||||
| APL\360 | Ambiguous | No | No | Single | Yes | ||
| SHARP APL | Operator | Yes | No | Scalar | Yes | ||
| NARS, NARS2000 | Function | Yes | Yes | No | Single | Yes | |
| Dyalog APL | Function | Yes | Yes | No | APL2 (13.1) | Yes | |
| APL2 | Operator | Yes | No | Yes | APL2 | Yes | |
A+ (/) |
Function | Yes | No | Single | No | ||
J (#) |
Function | Yes | No | Scalar | No | Complex left argument allowed | |
| ISO/IEC 13751:2001 | Function | Yes | No | Scalar | Yes | ||
| APLX | Operator | Yes | Yes | Yes | APL2 | Yes | |
| ngn/apl | Ambiguous | Yes | Yes | No | APL2 | Yes | Implemented as an operator |
| GNU APL | Function | Yes | No | Yes | APL2 | Yes | |
dzaima/APL (⌿) |
Function | Yes | Yes | No | No | No | |
BQN (/) |
Function | Yes | No | No | No | Multiple leading axes supported | |
In each language without axis specification, there is only one form of Replicate, which applies to the first axis or major cells—the last-axis form is discarded. BQN extends this form to allow any number of leading axes to be manipulated if the left argument has depth 2.
Implementation
Replicate is suitable for both branchless scalar and vector instruction CPU implementations. When the values in the left argument are relatively small, this may be considerably faster than an implementation that loops for each of them, because each such loop can trigger a slow branch misprediction. Because it can made faster with dedicated code, language implementations frequently handle Compress, where the left argument is boolean, as a special case of the more general Replicate.[5][6]
Compress may be performed with a fast general-purpose function for each right argument type, possibly with additional specialization based on statistics of the left argument. A general-purpose scalar method that avoids branch mispredictions is to write every right argument value to the result but increment the destination pointer only when a 1 appears in the left argument, so that values corresponding to a 0 are overwritten.[7] Many other methods work faster if the left argument has a bit boolean representation; in J, which uses byte booleans, sections of the left argument are sometimes packed to a bit boolean temporary register.[5] These include storing a lookup table of Indices results to use with a SIMD shuffle instruction.[7] Special cases where the left argument has few 1 values, or consists mainly of long runs of 0 or 1, can be detected and may be faster with a branching implementation that searches through it with count trailing zeros.[8]
Some extensions to the x86 instruction set effectively perform Compress on particular data types. In BMI2, the PEXT and PDEP instructions (parallel bit extract and deposit) are identical to Compress and Expand on the bits of a register argument—however these are micro-coded and should be avoided on AMD's Zen, Zen+, and Zen 2 architectures.[9] The AVX-512 instructions VPCOMPRESSQ and VPEXPANDQ (and variations) are not only equivalent to Compress and Expand using a mask register for the Boolean argument and a vector register for the other argument, but are named after the APL functions. The AVX-512F instructions apply to 4- and 8-byte elements, and AVX-512_VBMI2 extends this to 1- and 2-byte elements.
For Replicate, if values in the left argument are large, then a looping implementation is generally sufficient: it will spend most of its time in the inner loop, which typical compilers can vectorize. For smaller sizes, multi-pass implementations based on Scan may be an improvement. These take differences on the argument, write them to appropriate indices of the result (combining values when they have the same index because a 0 appears in the left argument), and then undo the differences with a scan.[10] For a small constant left argument, numerous methods are possible, including shuffle instructions with pre-computed indices.[11]
See also
External Links
- ArrayCast - "Episode 110: Implementing Replicate". First part in the "Primitive Talk" series on array primitives.
Lessons
Documentation
References
- ↑ Benkard, J. Philip. "Replicate each, anyone?". APL87.
- ↑ Falkoff, A.D., and K.E. Iverson, "APL\360 User's Manual". IBM, August 1968.
- ↑ Bernecky, Bob. SATN-34: Replication. IPSA. 1980-08-15.
- ↑ IPSA. SHARP APL Reference Manual Additions and Corrections, June 1981 p.3: "extended definition adopted 8 February 1980".
- ↑ 5.0 5.1 Conor Hoekstra and others. Implementing Replicate. The Array Cast episode 110 (Primitive Talk #1). 2025-07-18.
- ↑ Marshall Lochbaum. Replicate and Indices benchmarks in BQN. Bencharray. Accessed 2025-08-18.
- ↑ 7.0 7.1 Marshall Lochbaum. Implementation of Indices and Replicate. Booleans. Accessed 2025-08-18.
- ↑ Marshall Lochbaum. The Interpretive Advantage (slides) at Dyalog '18.
- ↑ Marshall Lochbaum. Implementation of Indices and Replicate. Boolean compress. Accessed 2025-08-18.
- ↑ Marshall Lochbaum. Expanding Bits in Shrinking Time. Dyalog blog. 2018-06-11.
- ↑ Marshall Lochbaum. Implementation of Indices and Replicate. Replicate. Accessed 2025-08-18.