3 min read

Going down the branchless FizzBuzz rabbit hole in APL

I read a bit about APL earlier this week from Xpqz's Learn APL and decided to try FizzBuzz in APL using TryAPL. How hard could it be, right?

Wrong.

Here's what the finished program looked like:

⍝ Explicitly set the first index, because it affects iota (⍳, as in ⍳100).
⍝ Note 1 is the default first index.
⎕IO ← 1
range ← ⍳100
output ← ↓(⍕⍤0)range
((0=3|range)/output) ← ⊂'fizz'
((0=5|range)/output) ← ⊂'buzz'
((0=15|range)/output) ← ⊂'fizzbuzz'
⎕ ← ↑output

Breaking this down:

  1. We set the first index. APL is 1-indexed by default (so the first index is 1), but you can set it to be 0-indexed, so just in case we explicitly set a 1-index.
  2. range is assigned the vector of integers from 1 to 100, inclusive.
  3. output is assigned a vector of boxed string versions of each integer. Ordinarily the format operator would format the vector itself as a string, so we would get the string '1 2 3 4 ... 100' (where ... is replaced with the intervening numbers, separated by spaces), i.e. a string 291 characters long. I use ⍕⍤0 to convert the format operator to operate elementwise over its operand. Finally, converts the result (a 100x3 character matrix) to a vector of boxed strings by boxing the rows.
  4. ((0=3|range)/output) ← ⊂'fizz' is a special assignment statement. 0=3|range is a vector of booleans (boolean mask). It's 1 at each element of range that is divisible by 3 and 0 everywhere else. (mask/vector) ← value is a special assignment form that assigns to only those parts of vector corresponding to 1s in the mask. So this says: Assign the boxed string 'fizz' where the number is divisible by 3. The other two statements work similarly.
  5. ⎕ ← ↑output produces output by assigning to — this is how you print in APL. The converts output from a vector of boxed strings into a character matrix so that each string is printed on its own line. (This means trailing spaces also get printed. For example, we print 1       rather than 1. That's because the matrix has to be 10x8 to accomodate the fizzbuzz — every row in the matrix has to have the same number of columns.)

Some pain points that came up in trying to do this in APL:

  1. Initially I just did output ← (⍕⍤0)range, but this gives a 10x3 matrix of characters. I tried expanding to 10x8 with output ← ((⍕⍤0)range),(' '⍴⍨(≢range) 5), and this works fine, but things get annoying when you want to mask and set entire rows to 'fizz    ', 'buzz    ', or fizzbuzz based on the row number. I couldn't find a convenient way to do this based on a mask of 0=X|range. Eventually I settled on ↓(⍕⍤0)range which gives an array of 100 boxed 3-character strings.
  2. Initially I wanted to do the simpler mask/output ← value pattern, but I got a length error. I solved the length error by boxing the values. That's what the is for.
  3. However, for some reason when I did (0=3|range)/output ← ⊂'fizz' , this whole expression returned a long (33-element) array where every element is just the boxed string fizz. Worse, afterward the output was a vector containing only a single boxed string, namely the string 'fizz'! I tried a different approach using the @ operator, {⊂'fizz'}@{0=3|range}⊢output, and this seemed to work, but I think it is harder to read.
  4. I eventually figured out how to fix this. output was getting replaced with  ⊂'fizz' because the APL interpreter was interpreting the expression as (0=3|range)/(output←'fizz'). Parenthesizing the (mask/output) pair fixed the problem, so I was able to get rid of the more complicated {} notation.

I learned a few things about APL from this:

  1. Vectors of boxed strings are easier to work with in some cases than character matrices. You can turn a character matrix into a vector of boxed strings with ↓charMatrix.
  2. When assigning to a vector of boxed strings, you have to explicitly box the string(s) you assign. If you don't, you might get a length error.
  3. When using (mask/array) ← value, always parenthesize mask/array like that, otherwise you may get confusing results when value is a complex expression.

An interesting thing about this program is that it's branchless. We set a first-pass output (the boxed number strings). We then compute which outputs we want to change and then change only those indices. That means we never need an if-statement to decide which string to output.