Thank You Ian Sharp

On July 16th, one of the most influential founders of what we today refer to as the “array language community” died peacefully, a few months after being diagnosed with lung cancer (link: Toronto Globe and Mail).

Ian Patrick Sharp

In 1964, Ian Patrick Sharp formed I.P.Sharp Associates (IPSA), together with six colleagues who were made redundant when Ferranti-Packard closed its computer division in Toronto, Canada. As Ian explains in a wonderful interview that was recorded in 1984 (link: Snake Island website), he was approached by people who wanted to recruit the whole team. Instead, he decided to form a company, since the team obviously had significant value.

The company was involved in the first APL implementation at IBM (APL\360). Subsequently, IBM allowed them to modify and enhance the system, and built a timesharing service that became known as SHARP APL. Roger Moore was a co-founder of IPSA and, in addition to being responsible for the supervisor that made SHARP APL a superior timesharing system, Roger was the chief architect of IPSANET, one of the worlds first packet switched networks.

In the late 1970s the combination of APL and IPSANET was revolutionary, and IPSA quickly attracted business from global corporate clients who used SHARP APL for e-mail, reporting and analytics, and a rapidly-growing collection of financial timeseries data – all completely new technologies at the time. In particular, the transmission of data over telephone lines changed the world. Ian had many absurd encounters with telecom monopolies who tried to protect old business models or profit from the new technology (link: archive.org).

A Stylised Map of the I.P.Sharp Associates APL Time-Sharing Network

Ian’s management style perfectly matched – and drove – the revolutionary technologies. As Ian explains so eloquently and humorously in the interview, IPSA recruited talented people without necessarily having specific tasks in mind. Ian set the tone and direction and then let people get on with it, moving around in the background to get a sense of how things were going and making adjustments without ever making a fuss. IPSA was a fantastic place to work and attracted a wonderfully diverse (in the most modern sense of the word) collection of smart people who developed revolutionary tools, helped a lot of customers, had a lot of fun, and made money.

Ultimately, IPSA was creative, problem-oriented and customer-driven to the extent that it failed to respond to fundamental changes in the market in time. At the end of the 1980s the timesharing revenues suddenly faded, and the company was acquired by Reuters for its timeseries databases and more or less disappeared overnight. However, IPSA had acted as a fantastic breeding ground for technology and talent for a quarter century, and there are hundreds of people who fondly and gratefully remember Ian for the way that he allowed them all to grow.

I don’t think it is a coincidence that so many of the active array language organisations have key players who were once IPSA employees (some of them appearing in more than one place thanks to relationships forged a very long time ago 😊).

  • Jsoftware: Roger Hui, Eric Iverson, Chris Burke, Ken Iverson
  • Kx: Arthur Whitney, Simon Garland, Stephen Taylor, Chris Burke
  • Dyalog: Gitte Christensen, Morten Kromberg, Roger Hui, Brian Becker, Dan Baronet
  • Snake Island Research: Robert Bernecky

As always, Roger has collected anecdotes about IPSA, Ian and other people who worked there, which you can find on jsoftware.com/papers/SharpQA.htm.

My Own IPSA Story

In 1978, my dad was moving out of an apartment in Oslo. At the same time, XEROX insisted that IPSA open an office in Oslo to support their international business, and several Canadians arrived there. I helped move some furniture and, sensing a keen interest and real excitement in programming, the IPSA Oslo team offered me a free account to play with APL timesharing, if I was interested. I effectively became a piece of furniture in the IPSA office after school, and had keys to the office so I could come and go as I pleased. After a year or so, they started throwing me bits of real work to do and paying me for my time. I think I was 17 at the time.

In addition to working as an APL consultant and tool builder, one of the things I did in my spare time was to write a tool for myself that would compare the entire contents of the e-mail directory with its state at the end of the previous week. Since IPSA was 100% managed by e-mail groups, this allowed me to know instantly when a new office was opened, a significant new project was started, and, of course, when new employees joined the company. By using this technique of harvesting e-mail addresses and sending unsolicited e-mail when an interesting project or person joined, I found my future partner both at home and the office – Gitte, the current CEO of Dyalog Ltd – only about 500km away in the IPSA Copenhagen branch.

I spent about a decade at IPSA and, after its sudden disappearance, Gitte and I have been trying to recreate the IPSA atmosphere in every team that we have been a member of. In a very real sense, I owe not only my career but almost everything of value about my life to Ian Sharp and the warm and welcoming company that he created.

Thank You, Ian!

Code Golf: Generating Empty Arrays

By: Stefan Kruger

Recently, I was looking for a Dyalog problem to pit my wits against, and I asked in the APL Orchard if AdΓ‘m could make one up:

A CMC, if you’re unfamiliar with the APL Orchard, is a Chat Mini Challenge – typically an informal code golf challenge to produce the shortest possible bit of code that solves the set problem. Code golf practitioners usually delve into the dustiest corners of a language and, if practiced diligently, this can lead to a deep mastery over time, even if some dirty hacks techniques employed may be frowned upon in production code.

Anyway. AdΓ‘m responded with the following:

The Mysterious Case of 0 0⍴0

Hmm. What even is that thingβ€½ It’s only 5 characters already – not much scope to shrink that, surely? Let’s have a look at it with full boxing:

      βŽ•IO←0
      ]Box on -style=max
β”Œβ†’β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚Was ON -style=maxβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      0 0⍴0
β”ŒβŠ–β”
⌽0β”‚
β””~β”˜

In the boxed output, the ⌽ tells us that the leading axis has length 0, the βŠ– means that the trailing axis has length 0, and the ~ means that the array is non-nested.

This is a simple numeric array of two dimensions, each of length 0 – think of it as a spreadsheet or table before you’ve put any rows or columns of data in it.

I found this problem difficult to get started with, so I searched for the expression on APL Cart, and discovered that:

      ]Box off
Was ON
      ]APLCart 0 0⍴0   ⍝ Dyalog 18.1 has APLCart built in! Fancy.
X,Y,Z:any M,N:num I,J:int A,B:Bool C,D:char f,g,h:fn ax:axis s:scal v:vec m:mat
───────────────────────────────────────────────────────────────────────────────
⍬⊀⍬  zero-by-zero numeric matrix                                               
───────────────────────────────────────────────────────────────────────────────
Showing 1 of 1 matches                                                         
      ]Box on
β”Œβ†’β”€β”€β”€β”€β”€β”€β”
β”‚Was OFFβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”˜

Surely not? Let’s try that suggestion:

      (0 0⍴0)β‰‘β¬βŠ€β¬
1

Well, it clearly works, but why? Let’s see if we can figure that one out.

In the basic case for encode, X⊀Y, if X and Y are vectors, the result will have one column for each element in Y and one row for each element in X. Using the example from the language bar:

      2 2 2 2 ⊀ 5 7 12 ⍝ Convert decimal to binary
β”Œβ†’β”€β”€β”€β”€β”
↓0 0 1β”‚
β”‚1 1 1β”‚
β”‚0 1 0β”‚
β”‚1 1 0β”‚
β””~β”€β”€β”€β”€β”˜

we have a shape of 4 3, as the length of the vector to the left (2 2 2 2) is 4 and the length of the vector to the right (5 7 12) is 3.

Returning to ⍬⊀⍬, given the above, we can deduce that the result should have a rank of 2 with a shape of 0 0 which, of course, is what we wanted to achieve:

      ⍴⍬⊀⍬ ⍝ Shape 0 0
β”Œβ†’β”€β”€β”
β”‚0 0β”‚
β””~β”€β”€β”˜

Disappointingly, I had to cheat by looking up the answer. However, are there any more length-3 solutions? Apparently, ⍬⊀⍬ is “reasonably well known”. I decided to write a simple brute-force search function to look for any other solutions.

This works as follows:

  1. Create all possible length-3 combinations of APL glyphs
  2. Evaluate each in turn, skipping those that error
  3. Save those that evaluate to 0 0⍴0

Here’s what I ended up with:

βˆ‡ r←Bruter target;glyphs;combo;eval 
  glyphs ← '0⍬+-Γ—Γ·*βŸβŒΉβ—‹|⌈⌊βŠ₯⊀⊣⊒=≠≀<>β‰₯β‰‘β‰’βˆ¨βˆ§β²β±β†‘β†“βŠ‚βŠƒβŠ†βŒ·β‹β’β³βΈβˆŠβ·βˆͺ∩~/\βŒΏβ€,βͺβ΄βŒ½βŠ–β‰Β¨β¨β£.∘⍀β₯@βŒΈβŒΊβŽβ•Β―'
  r ← ⍬
  :For combo :In ,∘.,⍣2⍨glyphs
      :Trap 0
          eval ← ⍎combo
      :Else
          :Continue
      :EndTrap
      :If eval≑target
          r ,← βŠ‚combo
      :EndIf
  :EndFor
βˆ‡

I decided to leave out a few glyphs that I guessed would be unlikely to feature (β†β†’βŽ•β βžββ‹„), and I only added the number 0. Let’s see what that generates:

      7 5⍴Bruter 0 0⍴0 ⍝ 7Γ—5 layout added for clarity after the fact
β”Œβ†’β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
↓ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚β¬βŠ€β¬β”‚ β”‚+βŒΈβ¬β”‚ β”‚-βŒΈβ¬β”‚ β”‚Γ—βŒΈβ¬β”‚ β”‚Γ·βŒΈβ¬β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚*βŒΈβ¬β”‚ β”‚βŸβŒΈβ¬β”‚ β”‚β—‹βŒΈβ¬β”‚ β”‚|βŒΈβ¬β”‚ β”‚βŒˆβŒΈβ¬β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚βŒŠβŒΈβ¬β”‚ β”‚βŠ€β¨β¬β”‚ β”‚βŠ€βŒΈβ¬β”‚ β”‚βŠ’βŒΈβ¬β”‚ β”‚=βŒΈβ¬β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚β‰ βŒΈβ¬β”‚ β”‚β‰€βŒΈβ¬β”‚ β”‚<βŒΈβ¬β”‚ β”‚>βŒΈβ¬β”‚ β”‚β‰₯βŒΈβ¬β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚βˆ¨βŒΈβ¬β”‚ β”‚βˆ§βŒΈβ¬β”‚ β”‚β²βŒΈβ¬β”‚ β”‚β±βŒΈβ¬β”‚ │↑⍸0β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚β†‘βŒΈβ¬β”‚ β”‚β†“βŒΈβ¬β”‚ β”‚β·βŒΈβ¬β”‚ β”‚βˆ©βŒΈβ¬β”‚ β”‚/βŒΈβ¬β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚βŒΏβŒΈβ¬β”‚ β”‚β΄βŒΈβ¬β”‚ β”‚βŒ½βŒΈβ¬β”‚ β”‚βŠ–βŒΈβ¬β”‚ β”‚β‰βŒΈβ¬β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β””βˆŠβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Wow! There are 35 of them (for βŽ•IO←0)!

There are clearly patterns here – we can see our friend ⍬⊀⍬ right at the beginning, and also its equivalent ⊀⍨⍬. We can also see ↑⍸0 which, in retrospect, perhaps I should have thought of in the first place.

The remaining 32 solutions all feature key. The majority of those have a scalar function operand; we can treat this whole group as equivalent. Let’s tackle that group first, with the operand + as the example:

      +⌸⍬
β”ŒβŠ–β”
⌽0β”‚
β””~β”˜

Let’s recap what key does. The operand dyadic function is called with each unique element of the argument in turn as its left argument, and a vector of indices of occurrences as its right. Key then returns a rank 2 array of the results. For example:

      {⍺ ⍡}⌸'Mississippi'
β”Œβ†’β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
↓   β”Œβ†’β”        β”‚
β”‚ M β”‚1β”‚        β”‚
β”‚ - β””~β”˜        β”‚
β”‚   β”Œβ†’β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ i β”‚2 5 8 11β”‚ β”‚
β”‚ - β””~β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚   β”Œβ†’β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚ s β”‚3 4 6 7β”‚  β”‚
β”‚ - β””~β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚   β”Œβ†’β”€β”€β”€β”     β”‚
β”‚ p β”‚9 10β”‚     β”‚
β”‚ - β””~β”€β”€β”€β”˜     β”‚
β””βˆŠβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

With an argument of the empty vector ⍬, in the operand function, ⍺ will always be 0 – the prototype element of our empty numeric vector:

      {βŽ•β†βΊ}⌸⍬
0

What about ⍡? Well, it must be an empty numeric vector (no found indices):

      {βŽ•β†β΅}⌸⍬
β”ŒβŠ–β”
β”‚0β”‚
β””~β”˜

So, with a scalar function like + as the operand, we end up with 0+⍬. Key returns an array where each major cell is the result of the function applied to the unique element and its indices, which in this case is an array with major cells of the structure ⍬. How many such major cells? 0. So the result is 0⌿1 0⍴⍬, which is, of course, 0 0⍴0.

So for 0 f ⍬ for any scalar dyadic function f, the above holds. For example:

      0>⍬
β”ŒβŠ–β”
β”‚0β”‚
β””~β”˜
      >⌸⍬
β”ŒβŠ–β”
⌽0β”‚
β””~β”˜

Then we have a lot of non-scalar operands, like βŠ–/⍷↑↓. All we need to understand is why they produce ⍬ when applied as 0 f ⍬, as key will call them. Some of these are pretty obvious, like find, ⍷:

      0⍷⍬ ⍝ Find all locations of 0 in the empty vector
β”ŒβŠ–β”
β”‚0β”‚
β””~β”˜

or take and drop, ↑↓:

      0↑⍬ ⍝ Take no elements from the beginning of the empty vector
β”ŒβŠ–β”
β”‚0β”‚
β””~β”˜
      0↓⍬ ⍝ Drop no elements from the end of the empty vector
β”ŒβŠ–β”
β”‚0β”‚
β””~β”˜

However, ⍉ gives us a dyadic transpose – and, perhaps unexpectedly, this one is βŽ•IO-dependent:

      0⍉⍬
β”ŒβŠ–β”
β”‚0β”‚
β””~β”˜

At first glance, this made no sense to me. The reason this works is that transposing a vector is an identity operation:

      ⍉'Transposing a vector returns the vector'
β”Œβ†’β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚Transposing a vector returns the vectorβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

A vector has a single axis, so “reordering the axes” doesn’t do anything. The same holds true for the dyadic form, assuming the left argument is βŽ•IO:

      0⍉'Same for the dyadic form. Sometimes.'
β”Œβ†’β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚Same for the dyadic form. Sometimes.β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

This is the reason why this only works for βŽ•IO←0: Key always provides 0 for the left argument. For βŽ•IO←1 we will get a DOMAIN ERROR:

      βŽ•IO←1
      0⍉'Same for the dyadic form. Sometimes.'
DOMAIN ERROR
      0⍉'Same for the dyadic form. Sometimes.'
       ∧

A few others stand out. Replicate (and its sibling replicate-first), for example:

      /⌸⍬
β”ŒβŠ–β”
⌽0β”‚
β””~β”˜

will end up as:

      0/⍬ ⍝ zero empty vectors
β”ŒβŠ–β”
β”‚0β”‚
β””~β”˜

in the left operand – zero empty vectors is still the empty vector. Reshape is similar:

      0⍴⍬ ⍝ zero empty vectors
β”ŒβŠ–β”
β”‚0β”‚
β””~β”˜

Encode tries to express ⍬ in the 0 radix; also an empty vector:

      0⊀⍬
β”ŒβŠ–β”
β”‚0β”‚
β””~β”˜

My favourite, though, is probably ↑⍸0, and, as I said earlier, I’m a bit disappointed I didn’t find that before resorting to brute force and ignorance. Where (⍸) returns a vector of indices with 1 for its Boolean array right argument. If we call it with a right argument of 0, we’ll get an empty vector of empty numeric vectors. This is because the one (and only) valid index into a scalar is the empty vector, and none of them (!) are non-zero.

      ⍸0
β”ŒβŠ–β”€β”€β”€β”€β”
β”‚ β”ŒβŠ–β” β”‚
β”‚ β”‚0β”‚ β”‚
β”‚ β””~β”˜ β”‚
β””βˆŠβ”€β”€β”€β”€β”˜

Trading depth for rank, we get the shape we want:

      ↑⍸0
β”ŒβŠ–β”
⌽0β”‚
β””~β”˜

What About 0β΄βŠ‚β¬?

The expression ⍸0 above is the only length-2 way to produce an empty vector of empty numeric vectors:

      (⍸0)≑0β΄βŠ‚β¬
1

However, there are several interesting length-3 solutions. Deploying the Bruter again:

      8 7⍴res←Bruter 0β΄βŠ‚β¬
β”Œβ†’β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
↓ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚0βŠ‚0β”‚ β”‚0βŠ‚β¬β”‚ β”‚0βŠ†β¬β”‚ β”‚β¬βŠ‚0β”‚ β”‚β¬βŠ‚β¬β”‚ β”‚β¬βŠ†β¬β”‚ β”‚+⍸0β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚-⍸0β”‚ │×⍸0β”‚ │÷⍸0β”‚ β”‚*⍸0β”‚ β”‚βŸβΈ0β”‚ │○⍸0β”‚ β”‚|⍸0β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚βŒˆβΈ0β”‚ β”‚βŒŠβΈ0β”‚ β”‚βŠ£βΈ0β”‚ β”‚βŠ’βΈ0β”‚ β”‚βŠ‚β¨0β”‚ β”‚βŠ‚β¨β¬β”‚ β”‚βŠ†βΈ0β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚βŠ†β¨β¬β”‚ β”‚βŒ·βΈ0β”‚ │⍳¨⍬│ │⍸00β”‚ │⍸0.β”‚ │⍸+0β”‚ │⍸-0β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ │⍸×0β”‚ │⍸○0β”‚ │⍸|0β”‚ β”‚βΈβŒˆ0β”‚ β”‚βΈβŒŠ0β”‚ β”‚βΈβŠ£0β”‚ β”‚βΈβŠ’0β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ │⍸≑0β”‚ │⍸≒⍬│ │⍸↑0β”‚ │⍸↓0β”‚ β”‚βΈβŠ‚0β”‚ β”‚βΈβŠƒ0β”‚ β”‚βΈβŠƒβ¬β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚βΈβŠ†0β”‚ β”‚βΈβŒ·0β”‚ β”‚βΈβŒ½0β”‚ β”‚βΈβŠ–0β”‚ │⍸⍉0β”‚ │⍸.0β”‚ │⍸¯0β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚βˆͺ⍸0β”‚ β”‚~⍸0β”‚ β”‚,⍸0β”‚ │⍴¨⍬│ β”‚βŒ½βΈ0β”‚ β”‚βŠ–βΈ0β”‚ │⍉⍸0β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β””βˆŠβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Most are ⍸0 combined with an additional primitive that has no effect, or selfie-mirrors, so let’s restrict ourselves to the interesting subset:

      4 2⍴res/⍨~'⍸⍨'∘(∨/∊)¨res ⍝ Skip variants of ⍸0 and selfies
β”Œβ†’β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
↓ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚0βŠ‚0β”‚ β”‚0βŠ‚β¬β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚0βŠ†β¬β”‚ β”‚β¬βŠ‚0β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ β”‚β¬βŠ‚β¬β”‚ β”‚β¬βŠ†β¬β”‚ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ†’β”€β”€β” β”Œβ†’β”€β”€β” β”‚
β”‚ │⍳¨⍬│ │⍴¨⍬│ β”‚
β”‚ β””β”€β”€β”€β”˜ β””β”€β”€β”€β”˜ β”‚
β””βˆŠβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

We can see a few patterns again – partition (βŠ†) or partitioned enclose (βŠ‚), with 0 as left or right argument and ⍬ as left or right argument, plus two variants using each (Β¨) on ⍬. Let’s look at βŠ‚ first.

Partitioned enclose groups, or partitions, stretches its right argument as specified by its Boolean vector left argument, with each new partition starting on 1, and returns a nested vector of the resulting partitions:

      1 0 0 0 0 1 1 0 0 0 0βŠ‚'Hello World'
β”Œβ†’β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”Œβ†’β”€β”€β”€β”€β” β”Œβ†’β” β”Œβ†’β”€β”€β”€β”€β” β”‚
β”‚ β”‚Helloβ”‚ β”‚ β”‚ β”‚Worldβ”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β”‚
β””βˆŠβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

In the first case, 0βŠ‚0, we’re saying that we want 0 partitions of 0 (which gets treated as a 1-element vector), returned as a nested vector – in other words, exactly the “empty vector of empty numeric vectors” that we’re after. In fact, βŠ‚ with a 0 as its left argument returns an empty vector of empty vectors of the prototype element of the right, which also helps to explain our 0βŠ‚β¬:

      0βŠ‚'hello world' ⍝ Empty vector of empty character vectors
β”ŒβŠ–β”€β”€β”€β”€β”
β”‚ β”ŒβŠ–β” β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β””β”€β”˜ β”‚
β””βˆŠβ”€β”€β”€β”€β”˜
      0βŠ‚1 2 3 4 5 ⍝ Empty vector of empty numeric vectors
β”ŒβŠ–β”€β”€β”€β”€β”
β”‚ β”ŒβŠ–β” β”‚
β”‚ β”‚0β”‚ β”‚
β”‚ β””~β”˜ β”‚
β””βˆŠβ”€β”€β”€β”€β”˜
      0βŠ‚β¬ ⍝ Empty vector of empty numeric vectors
β”ŒβŠ–β”€β”€β”€β”€β”
β”‚ β”ŒβŠ–β” β”‚
β”‚ β”‚0β”‚ β”‚
β”‚ β””~β”˜ β”‚
β””βˆŠβ”€β”€β”€β”€β”˜

Swapping the order of that last expression:

      β¬βŠ‚0
β”ŒβŠ–β”€β”€β”€β”€β”
β”‚ β”ŒβŠ–β” β”‚
β”‚ β”‚0β”‚ β”‚
β”‚ β””~β”˜ β”‚
β””βˆŠβ”€β”€β”€β”€β”˜

is really the same thing: the left side is still a numeric vector with no 1s.

What about βŠ†? If we restrict ourselves to a Boolean left argument again, partition is similar to replicate, but instead of just skipping elements of the right side corresponding to 0s in the left side, it encloses groups of elements corresponding to 1s, and skips the rest:

      1 1 1 1 1 0 1 1 1 1 1βŠ†'Hello World'
β”Œβ†’β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”Œβ†’β”€β”€β”€β”€β” β”Œβ†’β”€β”€β”€β”€β” β”‚
β”‚ β”‚Helloβ”‚ β”‚Worldβ”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β”‚
β””βˆŠβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Compare with replicate:

      1 1 1 1 1 0 1 1 1 1 1/'Hello World'
β”Œβ†’β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚HelloWorldβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

As with βŠ‚, βŠ† returns a vector of vectors and, by the same reasoning, if the left argument contains no 1s, then the inner vector will be an empty vector of the prototype element of the right argument:

      0βŠ†1 2 23 34 
β”ŒβŠ–β”€β”€β”€β”€β”
β”‚ β”ŒβŠ–β” β”‚
β”‚ β”‚0β”‚ β”‚
β”‚ β””~β”˜ β”‚
β””βˆŠβ”€β”€β”€β”€β”˜

We should understand the remaining βŠ† variant, β¬βŠ†β¬, too. You might wonder why there is no 0βŠ†0, analogous to the 0βŠ‚0 we saw earlier; a fair question. However, it results in an error:

      0βŠ†0 ⍝ RANK ERROR
RANK ERROR
      0βŠ†0 ⍝ RANK ERROR
       ∧

The reason for this is that βŠ† is IBM’s APL2 primitive, supplied for compatibility reasons. It simply doesn’t treat a scalar right argument as a 1-element vector, unlike βŠ‚.

What remains are the two variants using the each operator: ⍳¨⍬ and ⍴¨⍬. Each always returns an array of the same shape as its right argument, in which each element is the result of applying the operand to the corresponding element in the argument.

By that reasoning, with an argument of ⍬ we’ll always end up with an empty vector. The prototype element is itself a vector, as both ⍳ and ⍴ returns vectors – and, in our specific case, empty numeric vectors, as given the argument ⍬.

Closing Remark

Many people – certainly including myself – find reasoning about the behaviour of empty arrays in Dyalog APL difficult. Going through exercises such as these can help to make this clearer.