A taste of APL


So I've been learning some APL lately. Yes it uses bizarre symbols, but it is actually kinda easy. As long as you remember the primitives & some function composing rules (e.g. (f g) X is f (g X) but (f g h) X is (f X) g (h X)), that is. Very hard if you do not.

I tried to write FizzBuzz. In Python this is straight-forward:

def fizzbuzz(x):
    if x % (3 * 5) == 0: return 'FizzBuzz'
    elif x % 3 == 0: return 'Fizz'
    elif x % 5 == 0: return 'Buzz'
    else: return x

print([fizzbuzz(i) for i in range(1, 101)])

it took me a while in APL because I'm not familiar with the primitives:

FizzBuzz←{0=(3×5)|⍵:'FizzBuzz'  0=(3|⍵):'Fizz'  0=(5|⍵):'Buzz'  ⍵}
FizzBuzz¨⍳100

This one should be fairly easy to understand. A|B means B%A so 0=(3×5)|⍵ basically means int(0==(w % (3*5))); ⍳A basically means range(1,A+1) in python, and A¨B basically means A(i) for i in B.

I know this feels very "non-functional"-ish and I know there's definitely room for improvement by instinct, but my long-time typescript/python-marinated brain could not figure it out. And then I saw this solution:

FizzBuzz2←{('FizzBuzz' 'Fizz' 'Buzz' ⍵)[(0=((3×5) 3 5 ⍵)|⍵)⍳1]}

this is like this in python:

def fizzbuzz2(x):
    return (
        ['FizzBuzz', 'Fizz', 'Buzz', x][
            [int(0 == (x % i)) for i in [3*5, 3, 5, x]].index(1)
        ]
    )

which is absolutely non-pythonic in any way; yet it feels very natural to do in APL, it feels like this is how you're supposed to use the language.

There's one thing: functions in APL sometimes applies to every elements in an array "automatically", e.g. 1 + (3 4 5) evaluates to 4 5 6, which is why we can directly ((3×5) 3 5 ⍵)|⍵ because it will become (3×5)|⍵ 3|⍵ 5|⍵ ⍵|⍵ and also why we can directly do a 0= because it will become (0=(3×5)|⍵) (0=3|⍵) (0=5|⍵) (0=⍵|⍵).

Now, a lot of stuff in APL are vector/matrix operations; it's aimed at mathematicians rather than normal programmers, e.g. there's this primitive which is used to form matrices when used as a binary operator:

      We already know what ⍳ as an unary operator does
      100
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
      30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
      54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
      78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

      Generate a 3-row 4-column matrix:
      3 4  100
1  2  3  4
5  6  7  8
9 10 11 12

of course it can form n-dimension arrays as well:

      2 3 4  100
 1  2  3  4
 5  6  7  8
 9 10 11 12

13 14 15 16
17 18 19 20
21 22 23 24

and there's this primitive in Dyalog APL called the "Quad Diamond", it has the function named "Stencil", whose function is probably better described using an example:

      ⊢ is the identity function, basically "lambda x: x"
      (⊢⌺3 3)(4 4  100)
 0  0  0
 0  1  2
 0  5  6

 0  0  0
 1  2  3
 5  6  7

 0  0  0
 2  3  4
 6  7  8

 0  0  0
 3  4  0
 7  8  0


 0  1  2
 0  5  6
 0  9 10

 1  2  3
 5  6  7
 9 10 11

 2  3  4
 6  7  8
10 11 12

 3  4  0
 7  8  0
11 12  0


 0  5  6
 0  9 10
 0 13 14

 5  6  7
 9 10 11
13 14 15

 6  7  8
10 11 12
14 15 16

 7  8  0
11 12  0
15 16  0


 0  9 10
 0 13 14
 0  0  0

 9 10 11
13 14 15
 0  0  0

10 11 12
14 15 16
 0  0  0

11 12  0
15 16  0
 0  0  0

like hell normal webdev will ever need to use that, but it's going to be very useful in some context (e.g. some kind of discrete convolution maybe? idk i'm not an applied math expert)

I thought it would be very handy to do digital image processing, so I did some.

Digital image processing

Mean Blur

I knew that Quad Diamond operator is going to be useful…

      MeanBlur←{({((+/,)÷(≢,))⍵}⌺⍺) ⍵}
      3 3 MeanBlur 4 4  100
1.555555556  2.666666667  3.333333333 2.444444444
3.666666667  6            7           5          
6.333333333 10           11           7.666666667
5.111111111  8            8.666666667 6          

It works as follows:

  • does its thingy with function {((+/,)÷(≢,))⍵} and left operand , and apply the result to MeanBlur's right operand ;
  • Function {((+/,)÷(≢,))⍵} does a ((+/,)÷(≢,)) on its right operand, which results in doing ((+/,)⍵) ÷ (≢,⍵);
  • From the behaviour of we know the is a matrix, we "unfold" the matrix with unary ,, and then calculate the sum with +/;
  • We do the same thing again, but this time instead of calculating the sum we calculate the length with unary ;
  • And then we divide the sum and the length, giving us the mean.

Laplacian Operator (or any kind of convolution operators, really)

We can do the same thing. It's basically cheating that we have this primitive in APL. We should just make a convolution function:

⍴A (unary ⍴) means "the shape of A"
Conv←{op←⍺  ({+/,op×⍵}⌺(⍴⍺))⍵}

and use it to define other functions:

the ({⍵⍴÷(×/⍵)}⍺) part constructs the mean matrix
MeanBlur←{({⍵⍴÷(×/⍵)}⍺) Conv ⍵}
Laplacian←{(3 3  0 ¯1 0 ¯1 4 ¯1 0 ¯1 0) Conv ⍵}
Sobel←{
    h_op3 3  (1 0 ¯1 2 0 ¯2 1 0 ¯1)
    ⍉ is the matrix transpose function
    v_op←⍉h_op
    h_convh_op Conv 
    v_convv_op Conv 
    (÷2)*⍨ is the square root function. (÷2) means 1/2,
    A*B means A to the power of B and A f⍨ B equals to B f A.
    h_conv {(÷2)*⍨ (⍺*2) + (⍵*2)} v_conv
}

Is having Stencil actually cheating though?

Yes Stencil is a Dyalog APL thing, but we can achieve this in other way as well. Assume we have a matrix a:

      a  4 4  100
      a
 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15 16

We have primitives that rotates it horizontally & vertically:

      1  a
 2  3  4  1
 6  7  8  5
10 11 12  9
14 15 16 13
      1  a
 5  6  7  8
 9 10 11 12
13 14 15 16
 1  2  3  4

We can first extend a by doing this:

      (2+⍴a)↑a
 1  2  3  4 0 0
 5  6  7  8 0 0
 9 10 11 12 0 0
13 14 15 16 0 0
 0  0  0  0 0 0
 0  0  0  0 0 0

and then rotate it around:

      ¯1  ¯1  (2+⍴a)↑a
0  0  0  0  0 0
0  1  2  3  4 0
0  5  6  7  8 0
0  9 10 11 12 0
0 13 14 15 16 0
0  0  0  0  0 0

This function we'll call it Extend:

Extend←{¯1  ¯1  (2+⍴⍵)↑⍵}

And we'll define A as Extend a.

      A  Extend a
      A
0  0  0  0  0 0
0  1  2  3  4 0
0  5  6  7  8 0
0  9 10 11 12 0
0 13 14 15 16 0
0  0  0  0  0 0

We can easily take the upper-left 3x3 corner by doing this:

      3 3  A
0 0 0
0 1 2
0 5 6

We wrap A with unary and perform a mapped horizontal rotate:

     0 1 2 ⌽¨ A
0  0  0  0  0 0   0  0  0  0 0 0   0  0  0 0 0  0 
0  1  2  3  4 0   1  2  3  4 0 0   2  3  4 0 0  1 
0  5  6  7  8 0   5  6  7  8 0 0   6  7  8 0 0  5 
0  9 10 11 12 0   9 10 11 12 0 0  10 11 12 0 0  9 
0 13 14 15 16 0  13 14 15 16 0 0  14 15 16 0 0 13 
0  0  0  0  0 0   0  0  0  0 0 0   0  0  0 0 0  0 

And then we can do an outer product of vertical rotation:

      this is for prettier printing
      )copy dfns disp
      disp 0 1 2 (∘.⊖) 0 1 2 (⌽¨) A
┌───────────────┬───────────────┬───────────────┐
│0  0  0  0  0 00  0  0  0 0 00  0  0 0 0  0│
│0  1  2  3  4 01  2  3  4 0 02  3  4 0 0  1│
│0  5  6  7  8 05  6  7  8 0 06  7  8 0 0  5│
│0  9 10 11 12 09 10 11 12 0 010 11 12 0 0  9│
│0 13 14 15 16 013 14 15 16 0 014 15 16 0 0 13│
│0  0  0  0  0 00  0  0  0 0 00  0  0 0 0  0│
├───────────────┼───────────────┼───────────────┤
│0  1  2  3  4 01  2  3  4 0 02  3  4 0 0  1│
│0  5  6  7  8 05  6  7  8 0 06  7  8 0 0  5│
│0  9 10 11 12 09 10 11 12 0 010 11 12 0 0  9│
│0 13 14 15 16 013 14 15 16 0 014 15 16 0 0 13│
│0  0  0  0  0 00  0  0  0 0 00  0  0 0 0  0│
│0  0  0  0  0 00  0  0  0 0 00  0  0 0 0  0│
├───────────────┼───────────────┼───────────────┤
│0  5  6  7  8 05  6  7  8 0 06  7  8 0 0  5│
│0  9 10 11 12 09 10 11 12 0 010 11 12 0 0  9│
│0 13 14 15 16 013 14 15 16 0 014 15 16 0 0 13│
│0  0  0  0  0 00  0  0  0 0 00  0  0 0 0  0│
│0  0  0  0  0 00  0  0  0 0 00  0  0 0 0  0│
│0  1  2  3  4 01  2  3  4 0 02  3  4 0 0  1│
└───────────────┴───────────────┴───────────────┘

And we do a 3x3 take on each of them:

      disp {3 3 ↑⍵}¨0 1 2 3 (∘.⊖) 0 1 2 3 (⌽¨) A
┌───────┬────────┬────────┬───────┐
│0 0 00 0 00 0 00 0 0  │
│0 1 21 2 32 3 43 4 0  │
│0 5 65 6 76 7 87 8 0  │
├───────┼────────┼────────┼───────┤
│0 1  21  2  32  3  43  4 0│
│0 5  65  6  76  7  87  8 0│
│0 9 109 10 1110 11 1211 12 0│
├───────┼────────┼────────┼───────┤
│0  5  65  6  76  7  87  8 0│
│0  9 109 10 1110 11 1211 12 0│
│0 13 1413 14 1514 15 1615 16 0│
├───────┼────────┼────────┼───────┤
│0  9 109 10 1110 11 1211 12 0│
│0 13 1413 14 1514 15 1615 16 0│
│0  0  00  0  00  0  00  0 0│
└───────┴────────┴────────┴───────┘

We can abstract away the 0 1 2 3 part because it's related to the size of A.

      A-⍨B means subtract A from B
      disp {3 3 ↑⍵}¨(1-⍨⍳2-⍨≢A) (∘.⊖) (1-⍨⍳2-⍨≢A) (⌽¨) A
┌───────┬────────┬────────┬───────┐
│0 0 00 0 00 0 00 0 0  │
│0 1 21 2 32 3 43 4 0  │
│0 5 65 6 76 7 87 8 0  │
├───────┼────────┼────────┼───────┤
│0 1  21  2  32  3  43  4 0│
│0 5  65  6  76  7  87  8 0│
│0 9 109 10 1110 11 1211 12 0│
├───────┼────────┼────────┼───────┤
│0  5  65  6  76  7  87  8 0│
│0  9 109 10 1110 11 1211 12 0│
│0 13 1413 14 1514 15 1615 16 0│
├───────┼────────┼────────┼───────┤
│0  9 109 10 1110 11 1211 12 0│
│0 13 1413 14 1514 15 1615 16 0│
│0  0  00  0  00  0  00  0 0│
└───────┴────────┴────────┴───────┘

And we can unfold the whole thing using , and we get roughly the same thing as (⊢⌺3 3)(4 4 ⍴ ⍳100):

      disp ,{3 3 ↑⍵}¨(1-⍨⍳2-⍨≢A) (∘.⊖) (1-⍨⍳2-⍨≢A) (⌽¨) A
┌─────┬─────┬─────┬─────┬──────┬───────┬────────┬───────┬───────┬────────┬─────
│0 0 00 0 00 0 00 0 00 1  21  2  32  3  43  4 00  5  65  6  76  70 1 21 2 32 3 43 4 00 5  65  6  76  7  87  8 00  9 109 10 1110 110 5 65 6 76 7 87 8 00 9 109 10 1110 11 1211 12 00 13 1413 14 1514 15
└─────┴─────┴─────┴─────┴──────┴───────┴────────┴───────┴───────┴────────┴─────

      ───┬───────┬───────┬────────┬────────┬───────┐
        87  8 00  9 109 10 1110 11 1211 12 01211 12 00 13 1413 14 1514 15 1615 16 01615 16 00  0  00  0  00  0  00  0 0│
      ───┴───────┴───────┴────────┴────────┴───────┘

We can come back to the Extend function & abstract away the 3x3 part but I'm too lazy to do that for this blogpost.

Conclusion

It's fun. It's not like I have learnt a lot about programming in general from learning APL because I do have a functional programming background (I mostly use Standard ML and Racket for my personal projects), but it's really fun. Sometimes in other languages (even functional languages) is recommended to apply function manually and doing tacit/point-free is considered to produce less readable code, but in APL that's not essentially the case: you can write very terse code if you're really good at composing functions in the APL way and the code will remain fairly readable. e.g. this function calculates a step of Game of Life:

life←{⊃ 1  ∨.∧ 3 4 = +/, ¯1 0 1 ∘.⊖ ¯1 0 1 ∘.⌽ ⊂⍵}

The ¯1 0 1 ∘.⊖ ¯1 0 1 ∘.⌽ part uses the same technique mentioned above.

Will I ever use APL for serious projects? Not until I want to do data-related stuff (e.g. machine learning or digital image processing), but even then I might choose something else: it's not like if it's good people will 100% choose it; sometimes you just have to compromise & choose another language (even if it's not as good) because everybody uses it, tons of pieces of software built using it, on it and around it, and if you don't use it you're missing out.


Back

© Sebastian Higgins 2021 All Rights Reserved.
Content on this page is distributed under the CC BY-NC-SA 4.0 license unless further specified.
All code snippets on this page are in public domain.
Last update: 2021.2.28