On Hsu's Suggestivity and Idioms
Recently, Aaron Hsu posted a nice article discussing a way in which APL’s
algebraic properties absolutely shine. The article ends up exhibits two
expressions—⍸2<⌿0⍪⍵
and ⍸2>⌿⍵⍪0
—which respectively pick the first and
last elements of groups of \(1\)s in a boolean mask.
The punchline being how the simple algebraic change—<
into
>
—corresponds to meaningful semantics. This is precisely what Iverson’s
“Notation as a Tool of Thought” is about, also echoing the “power of good
notation” idea in 3Blue1Brown’s Triangle of Power video.
So, what about replacing <
with an arbitrary boolean function? Hsu gives a
nice table with the corresponding output, but the semantics of each are not
spelled out. Paul Mansour makes a head start in his Boolean Techniques
article, but I think we can go all the way:
Y 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 1 Description
{2<⌿0⍪⍵}¨Y 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 1-group head
{2<⌿1⍪⍵}¨Y 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1-group head, 1-boundary
{2<⌿⍵⍪0}¨Y 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0-group terminus
{2<⌿⍵⍪1}¨Y 1 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0-group terminus, 1-boundary
{2≤⌿0⍪⍵}¨Y 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 not 0-group head
{2≤⌿1⍪⍵}¨Y 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 not 0-group head, 1-boundary
{2≤⌿⍵⍪0}¨Y 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 0 not 1-group terminus
{2≤⌿⍵⍪1}¨Y 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 not 1-group terminus, 1-boundary
{2=⌿0⍪⍵}¨Y 1 0 1 1 0 1 1 1 1 0 1 1 0 1 1 0 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 0 tail
{2=⌿1⍪⍵}¨Y 0 0 1 1 0 1 1 1 0 0 1 1 0 1 1 0 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 0 tail, 1-boundary
{2=⌿⍵⍪0}¨Y 0 1 1 0 1 1 1 1 0 1 1 0 1 1 0 0 1 1 1 0 1 1 1 1 1 1 1 0 1 1 0 0 not terminus
{2=⌿⍵⍪1}¨Y 0 1 1 0 1 1 1 0 0 1 1 0 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 0 1 not terminus, 1-boundary
{2≠⌿0⍪⍵}¨Y 0 1 0 0 1 0 0 0 0 1 0 0 1 0 0 1 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 head
{2≠⌿1⍪⍵}¨Y 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 head, 1-boundary
{2≠⌿⍵⍪0}¨Y 1 0 0 1 0 0 0 0 1 0 0 1 0 0 1 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 1 terminus
{2≠⌿⍵⍪1}¨Y 1 0 0 1 0 0 0 1 1 0 0 1 0 0 1 0 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 terminus, 1-boundary
{2≥⌿0⍪⍵}¨Y 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 0 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 not 1-group head
{2≥⌿1⍪⍵}¨Y 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 not 1-group head, 1-boundary
{2≥⌿⍵⍪0}¨Y 0 1 1 1 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 not 0-group terminus
{2≥⌿⍵⍪1}¨Y 0 1 1 1 1 1 1 0 0 1 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 1 not 0-group terminus, 1-boundary
{2>⌿0⍪⍵}¨Y 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0-group head
{2>⌿1⍪⍵}¨Y 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0-group head, 1-boundary
{2>⌿⍵⍪0}¨Y 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 1-group terminus
{2>⌿⍵⍪1}¨Y 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 1-group terminus, 1-boundary
{2∧⌿0⍪⍵}¨Y 0 0 1 1 0 0 0 0 0 0 1 1 0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 0 1-group tail
{2∧⌿1⍪⍵}¨Y 0 0 1 1 0 0 0 0 0 0 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1-group tail, 1-boundary
{2∧⌿⍵⍪0}¨Y 0 1 1 0 0 0 0 0 0 1 1 0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 0 0 1-group non-termius
{2∧⌿⍵⍪1}¨Y 0 1 1 0 0 0 0 0 0 1 1 0 0 0 0 1 1 1 1 0 0 0 0 0 1 1 1 0 0 0 0 1 1-group non-terminus, 1-boundary
{2⍲⌿0⍪⍵}¨Y 1 1 0 0 1 1 1 1 1 1 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 not 1-group tail
{2⍲⌿1⍪⍵}¨Y 1 1 0 0 1 1 1 1 1 1 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 not 1-group tail, 1-boundary
{2⍲⌿⍵⍪0}¨Y 1 0 0 1 1 1 1 1 1 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1 not 1-group non-terminus
{2⍲⌿⍵⍪1}¨Y 1 0 0 1 1 1 1 1 1 0 0 1 1 1 1 0 0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 0 not 1-group non-terminus, 1-boundary
{2∨⌿0⍪⍵}¨Y 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0 1 1 1 1 1 0 0 1 not 0-group tail
{2∨⌿1⍪⍵}¨Y 1 1 1 1 1 0 0 0 1 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0 1 1 1 1 1 0 0 1 not 0-group tail, 1-boundary
{2∨⌿⍵⍪0}¨Y 1 1 1 1 0 0 0 0 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0 0 1 1 1 1 0 0 1 1 not 0-group non-terminus
{2∨⌿⍵⍪1}¨Y 1 1 1 1 0 0 0 1 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0 1 1 1 1 1 0 0 1 1 not 0-group non-terminus, 1-boundary
{2⍱⌿0⍪⍵}¨Y 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 0 0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 0 0-group tail
{2⍱⌿1⍪⍵}¨Y 0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 0 0-group tail, 1-boundary
{2⍱⌿⍵⍪0}¨Y 0 0 0 0 1 1 1 1 0 0 0 0 1 1 0 0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 0 0 0-group non-terminus
{2⍱⌿⍵⍪1}¨Y 0 0 0 0 1 1 1 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 0 0 0-group non-terminus, 1-boundary
{2⊢⌿0⍪⍵}¨Y 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 1 id
{2⊢⌿1⍪⍵}¨Y 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 1 id
{2⊢⌿⍵⍪0}¨Y 1 1 1 0 0 0 0 0 1 1 1 0 0 0 1 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 1 0 left-shift, 0-insert
{2⊢⌿⍵⍪1}¨Y 1 1 1 0 0 0 0 1 1 1 1 0 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 1 1 left-shift, 1-insert
{2⊣⌿0⍪⍵}¨Y 0 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 right-shift, 0-insert
{2⊣⌿1⍪⍵}¨Y 1 0 1 1 1 0 0 0 1 0 1 1 1 0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1 0 0 0 right-shift, 1-insert
{2⊣⌿⍵⍪0}¨Y 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 1 id
{2⊣⌿⍵⍪1}¨Y 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 1 id
It is interesting to note that the naming scheme can be thought as a simple re-parameterization of the algebraic phrases. In particular, we have
\[ \begin{aligned} 48 &= \textrm{#functions} \\ &= B \cdot e \cdot d \\ &= 12 \cdot 2 \cdot 2 \end{aligned} \]
where
- \(B\) denotes the \(12\) count of binary functions you chose, which ignores the
other four binary functions,
0⍨
,1⍨
,~⍤⊢
, and~⍤⊣
, - \(e\) denotes the \(2\) choices, \(0\) or \(1\), of edge conditions, and
- \(d\) denotes the \(2\) choices, pre- or post-pending, of direction.
Whereas the semantic descriptions partition the collection differently:
\[ \begin{aligned} 48 &= \textrm{#functions} \\ &= g \cdot p \cdot d \cdot b \cdot i + A + S + I \\ &= 2 \cdot 2 \cdot 2 \cdot 2 \cdot 2 + 8 + 4 + 4 \end{aligned} \]
where
- \(g\) denotes the choice of selecting on either \(0\)- or \(1\)-groups,
- \(p\) denotes the selecting the group head or tail,
- \(d\) denotes the choice of “direction”, i.e. forwards selects head or tail and reverse selects terminus or non-terminus,
- \(b\) denotes the choice of \(0\) or \(1\) boundary condition,
- \(i\) denotes the choice of whether selection is inverted or not,
- \(A\) is the class of group-agnostic functions,
- \(S\) is the special class of shift functions, and
- \(I\) is the quadruple-counted identity function.
Just for fun, we can actually complete the table above, with the missing boolean functions:
Y 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 1
{2(0⍨)⌿⍵⍪0}¨Y 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 0 0 0 0 constant 0
{2(0⍨)⌿⍵⍪1}¨Y 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 0 0 0 0 constant 0
{2(0⍨)⌿0⍪⍵}¨Y 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 0 0 0 0 constant 0
{2(0⍨)⌿1⍪⍵}¨Y 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 0 0 0 0 constant 0
{2(1⍨)⌿⍵⍪0}¨Y 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 constant 1
{2(1⍨)⌿⍵⍪1}¨Y 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 constant 1
{2(1⍨)⌿0⍪⍵}¨Y 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 constant 1
{2(1⍨)⌿1⍪⍵}¨Y 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 constant 1
{2~⍤⊢⌿⍵⍪0}¨Y 0 0 0 1 1 1 1 1 0 0 0 1 1 1 0 1 0 0 0 1 1 1 1 1 0 0 0 1 1 1 0 1 invert left-shift, 1-insert
{2~⍤⊢⌿⍵⍪1}¨Y 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 0 invert left-shift, 0-insert
{2~⍤⊢⌿0⍪⍵}¨Y 1 0 0 0 1 1 1 1 1 0 0 0 1 1 1 0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 invert
{2~⍤⊢⌿1⍪⍵}¨Y 1 0 0 0 1 1 1 1 1 0 0 0 1 1 1 0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 invert
{2~⍤⊣⌿⍵⍪0}¨Y 1 0 0 0 1 1 1 1 1 0 0 0 1 1 1 0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 invert
{2~⍤⊣⌿⍵⍪1}¨Y 1 0 0 0 1 1 1 1 1 0 0 0 1 1 1 0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 invert
{2~⍤⊣⌿0⍪⍵}¨Y 1 1 0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 invert right-shift, 1-insert
{2~⍤⊣⌿1⍪⍵}¨Y 0 1 0 0 0 1 1 1 0 1 0 0 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 invert right-shift, 0-insert
Which makes it manifest why they can be safely ignored.