NOTE: if you are not seeing several Greek looking letters and arrows in the line above, some UTF8 stuff got wrong. Sorry, I'll try to fix it, drop me a tweet!
Nope. This is not line noise, but a complete APL program to display the Mandelbrot set in glorious ASCII, with asterisks and spaces just like Brooks and Matelski did a long time ago while studying Kleinian groups (it's a short paper, read it someday). I'll explain in (a lot, I hope) detail this line in a few moments. So, what's APL first?
APL is literally A Programming Language. Funny, isn't it? Its origins can be traced back to a way to formalise algorithms created by Kenneth Iverson while at Harvard. It is famed as a very cryptic language, for a reason. Its fundamental functions are unicode chars, and at first it feels extremely weird.
I got interested in it after finding an iOS interpreter for J, a programming language developed as a successor to APL, also by K.I. It just got away without Unicode, leaving only cryptic dygraphs, like <.@o. (from a Wikipedia example of high precision computing.) J is very cool, in that it offers a terse syntax to operate with matrices and vectors, very much like Matlab (Octave in my case) or R. But I felt I'd rather check the first idea, before diving into the even weirder syntax of J. So, it was APL.
The first problem was finding a good APL interpreter. Most were paid (and extremely expensive) and I could only find 2 APL interpreters working in Mac OS. One is NGN APL an implementation focused on node.js Also usable on a browser, so it is cool. But I'd rather get a little closer to the system... And decided to use Gnu APL instead. Of course, using an emacs mode for gnu-apl. Caveat: make sure to add
to your .zshrc or similar, without it you'll need to add a hook to gnu-apl mode reading
(set-buffer-process-coding-system 'utf-8 'utf-8)
to make sure emacs and apl talk in utf-8. Without it, there are no commands to send to the interpreter!
Since I'm also a heavy acme user, and acme is well-known in its unicode support, I used it instead to develop that line above. But emacs works correctly, I just like having things to tinker with acme.
Getting back to the line-noisiness of APL, first you need to know that precedence is right-to-left, so
in APL yields 12 instead of the expected 10. You can change this with parentheses. Now that I have taken this out, we can analyse the line above. Instead of explaining it from right to left, I'll explain how I proceeded to develop it.
Iterating the quadratic polynomial
Put it succintly (I could explain a lot of the mathematics behind, but I just want to do a picture today) to draw the Mandelbrot set we need to find complex points c=x+iy (remember: you can think of a complex point as a point in a plane, like a screen: it has an x coordinate and a y coordinate) such that computing
a lot of times with z=c is eventually a point outside a circle of radius 2. For instance, if c=1 this would be:
So the first part I wanted to get out of my way was generating this iteration. There are several ways to do it in APL, but a very cool way I found was to first generate the required computation like above. How?
computes exactly this, if m=c or if m is any other iterate, like m=f(f(f(c))). In APL speak, making m hold the same as c would be
and since right-to-left priority,
means "assign to m the value of c, multiply m by m (remember: it's the same as c) and add c." Likewise,
ends is computing the 2nd term in the list above, if c 1. So we have a pretty clear idea of how it should look like:
This is essentially "many times" the string " c+m×m" There's a very very easy way to repeat stuff in APL. For instance, 3 ⍴ 1 (read 3 reshape 1) creates a vector with three ones, 1 1 1. Reshape essentially multiplies or trims vectors. But... 3 ⍴ 'tomato' is 'tom'. The reshaping also cuts, of course! What we need is to make APL treat the string ' c+m×m' as one element. This is done with the next instruction in the Mandelbrot line, as you may guess: encapsulate.
ensures APL treats it as one element, so
generates a neat and long string of what the iterates should look like. To make sure the final string makes APL-sense, first we "enlist" with ∊. Turn the vector of repeated iterates into just one iterated element, like a long string all together. This could actually be removed, but it allowed me to learn a new function and I think it's pretty interesting. To end in a proper way, we add to the string a receiver variable 'z' and fuse the strings with comma.
I know this was long, but the iteration was the trickiest part!
Generating the grid of points
Since APL is cool with matrices (and complex numbers!) we can assign to the initial c a whole matrix of complex points (our screen pixels) and then we'll have all the stuff required for the picture.
This weird piece is just a compact way of generating a grid. To make it easier to understand, ⍳5 (that's iota 10) generates the sequence 1 2 3 4 5. To create a matrix we need two sequences (one for the x coordinates and another for the y coordinates.) For the complex numbers we can do 0J1 ⍳× 5 which yields 0J1 0J2 0J3 0J4 0J5. As you may guess, 0J1 is just the complex number i. For some weird reason APL uses the j letter instead of i. No big issue. Now we have the x's which go from 1 to 5 and the y's which go from i to 5i Now we need to find all the complex points in the plane. This means adding one of each x to each y, to get a grid. Similar to a multiplication table, but with sums instead of products. APL magic to the rescue!
(⍳5) .+ (0J1×(⍳5))
1J1 1J2 1J3 1J4 1J5
2J1 2J2 2J3 2J4 2J5
3J1 3J2 3J3 3J4 3J5
4J1 4J2 4J3 4J4 4J5
5J1 5J2 5J3 5J4 5J5
which is pretty close to what we need. To plot the Mandelbrot set we are only interested (mostly, and in this particular instance) in points with real coordinate x between -2.1 and 0.5 and complex coordinate between -1.3 and 1.3 (just to make it square which is easier.) This long blurb just adds a matrix similar to the one above to a complex point, generating the grid of points we want, and assigns it to c. Almost done!
Computing and plotting
After we have the set of points in c, we merge this string (with comma) to the iteration we developed above. At the far right of the iteration we find this piece
As you may guess, this adds to the string 1+0<|z which from right to left does:
- Compute the norm (distance to the origin) of the final iterate, which was assigned to z at the end of the iteration with |
- Check if this is larger than 4. This generates a matrix of 1s and 0s
- Add 1 to all entries of this matrix (so we have a matrix full of 1s and 2s
NOTE: The first version of this code had 0<|z, which works well for rough images of the Mandelbrot set, since most iterates are eventually INFNAN, which is a funny number in programming, since it is positive, smaller than 0, different from itself... Almost anything :D
We have a neat string which can generate a matrix of 1s and 2s and represent the Mandelbrot set. How do we make this string compute something? Magic!
Represent it neatly