APL Wiki logo: Difference between revisions
Miraheze>Marshall mNo edit summary |
m (26 revisions imported: Migrate from miraheze) |
(No difference)
|
Revision as of 14:49, 20 November 2019
The APL Wiki logo consists of the following numeric matrix, where each number indicates a circle radius:
⎕IO←0 ⌊∘.+⍨.5×4!⍨⍳5 1 2 3 2 1 2 4 5 4 2 3 5 6 5 3 2 4 5 4 2 1 2 3 2 1
This page explains, step-by-step, how we generate our SVG logo, using the above expression[1]. This demonstrates quite a few APL features.
We will follow APL's evaluation from right to left.
Counting
From 1 or from 0?
Whether to count from 0 or from 1 is an old disagreement among programmers. Many APLs let you choose whichever convention you want, but they tend to use 1 by default. To switch convention, we set the variable ⎕IO
:
⎕IO←0
By the way, IO stands for Index Origin.
We can already now observe a couple of APL's characteristics…
No reserved words
The name ⎕IO
begins with the special Quad character (a stylised console) which symbolises the computer system itself. APL has no reserved words. Rather, all built-in constants, variables, functions and operators have the prefix ⎕
indicating that they are part of the system. Because of this, we call them quad names.
Assignments
Assignment is not done with =
like in many other programming languages, but rather with ←
which also indicates the direction of the assignment: Whatever is on the right gets put into the name on the left.
Generating indices
The ⍳
function takes a number N and generates indices until is has made N indices. Since we set ⎕IO
to 0, we count from 0 until right before N:
⍳5 0 1 2 3 4
How many subsets?
Consider a bag with four distinct items. If you stick your hand into the bag and pick two items out, how many different possibilities are there for which pair you get out? . APL can tell you this with the Binomial (!
) function:
2!4 6
Notice how APL uses traditional mathematical symbols in a generalised way. The traditional post-fix (after its argument) symbol is used with a syntax similar to how you'd normally use or Failed to parse (syntax error): {\displaystyle ×} . In fact, all APL functions can be used infix, like or prefix, like .
Anyway, how many sets of four could you pick? Obviously, only one; all the items:
4!4 1
Automatic mapping
A really nice feature of APL is its array-orientation. For computations which are defined on single elements (scalar functions), mapping is implicit:
0 1 2 3 4!4 1 4 6 4 1
(What's up with picking zero out of four items? Since all empty hands are equal, there is exactly one such set — the empty set.)
Order of evaluation
We want to generate the indices using Iota (⍳
)…
⍳5!4
That didn't work! This is because APL dispenses with traditional mathematics' confusing and inconsistent precedence order[2], replacing it with a simple right-to-left rule:
(⍳5)!4 1 4 6 4 1
Swapping arguments
If the arguments of !
were swapped, we wouldn't need that parenthesis. Enter the operator (higher-order function) swap (⍨
) which takes a dyadic function on its left and creates a new derived function which is identical to the original, but has swapped arguments:
4!⍨⍳5 1 4 6 4 1
A number is a number
The next step is to halve everything:
.5×4!⍨⍳5 0.5 2 3 2 0.5
Notice how we were dealing with integers until now, but then we multiply by a float (non-integer). In APL, you don't need to worry about numeric data type conversions. All numeric types get automatically promoted and demoted as needed. APL implementations will usually use the most compact internal representation.
Traditional mathematical symbols
Also notice that we use a proper multiplication symbol, Failed to parse (MathML with SVG or PNG fallback (recommended for modern browsers and accessibility tools): Invalid response ("Math extension cannot connect to Restbase.") from server "https://wikimedia.org/api/rest_v1/":): {\displaystyle ×} , for multiplication. If traditional mathematics has a symbol for a concept APL includes then APL will use that symbol. Another example is Failed to parse (syntax error): {\displaystyle ÷} for division.
Tables
Remember the multiplication table from school?
1 2 3 4 5∘.×1 2 3 4 5 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25
Any function can be made into a table with the Outer Product:
1 2 3 4 5∘.+1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9 6 7 8 9 10
Using an argument twice
It gets tedious to type the same argument twice. Enter the selfie operator which shares its symbol with the above-mentioned swap operator. There's no ambiguity here. Swap swaps the two arguments, while selfie uses a single argument twice:
∘.+⍨1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9 6 7 8 9 10
We'll use this in our logo expression:
∘.+⍨.5×4!⍨⍳5 1 2.5 3.5 2.5 1 2.5 4 5 4 2.5 3.5 5 6 5 3.5 2.5 4 5 4 2.5 1 2.5 3.5 2.5 1
Rounding
The last step is to round these numbers down. Traditional mathematics writes floor as Failed to parse (syntax error): {\displaystyle ⌊x⌋}
but APL is regular, so no function is denoted by two separated symbols. If the function takes a single argument, then the symbol will be on the left, so we write floor as ⌊x
:
⌊∘.+⍨.5×4!⍨⍳5 1 2 3 2 1 2 4 5 4 2 3 5 6 5 3 2 4 5 4 2 1 2 3 2 1
Those are our radii! Let's save them:
r←⌊∘.+⍨.5×4!⍨⍳5
Placing the circles
Pairwise summation
Now that we have our radii, we need to figure out where to put our circles. The horizontal pair-wise sum shows how much adjacent circles "reach out" towards each other. N-wise Reduce solves that. Here, /
is an operator which takes the plus function and applies it in-between the elements of each horizontal run of length N (the left argument) in the right argument:
2+/sizes 3 5 5 3 6 9 9 6 8 11 11 8 6 9 9 6 3 5 5 3
Finding maxima
Since the circles line up on a grid, we need the maximum for each horizontal space, that is for each column. APL uses dyadic a⌈b
as the maximum of a and b. ⌈⌿
is the columnar maximum-reduction:
⌈⌿2+/sizes 8 11 11 8
But obviously, we can't let the circles touch, so we add 1. Finally, we prepend a 0 which is the offset of the first circle:
⊢offsets←0,+\1+⌈⌿2+/sizes 0 9 21 33 42
⊢
is the identity function, which is just used here get the pass-though value from the assignment, as it would otherwise be hidden. (We call assignment shy.)
Generating indices in 2D
⍴r
is the shape of our array of radii. Now, you remember ⍳
, right? As it turns out, it can actually generate of an array of any number of dimensions; two in our case:
⊢indices←⍳⍴r ┌───┬───┬───┬───┬───┐ │0 0│0 1│0 2│0 3│0 4│ ├───┼───┼───┼───┼───┤ │1 0│1 1│1 2│1 3│1 4│ ├───┼───┼───┼───┼───┤ │2 0│2 1│2 2│2 3│2 4│ ├───┼───┼───┼───┼───┤ │3 0│3 1│3 2│3 3│3 4│ ├───┼───┼───┼───┼───┤ │4 0│4 1│4 2│4 3│4 4│ └───┴───┴───┴───┴───┘
Now we take each coordinate (i.e. each rank 0 sub-array) from that and use it to index (⌷
) into the list (a rank 1 array) of offsets. The result is has shape…
⍴xy←indices⌷⍤0 1⊢offsets 5 5 2
that is, it is a 3D array with 5 layers, 5 rows in each layer, and 2 columns in each row. The Rank operator (⍤
) allows me to specify that I want rank 0 sub-arrays on the left paired up with rank-1 sub-arrays on the right. The ⊢
doesn't do anything other than separate 0 1
from offsets
, so the ⍤
knows what is its right operand (which specifies on what sub-arrays the function left operand is to be called) and what is its right argument (which contains the arguments for that function in its sub-arrays).
Each row of our result represents an value. The first and last layers, which represents the leftmost and rightmost columns of the logo, are:
(1↑xy) (¯1↑xy) ┌────┬─────┐ │0 0│42 0│ │0 9│42 9│ │0 21│42 21│ │0 33│42 33│ │0 42│42 42│ └────┴─────┘
E.g. the second row in the first layer is . ↑
is the Take function, that is, it takes the first N cells of an array, and a negative value simply means taking from the rear.
Making <circle/>
tags
To help us create our SVG <circle/>
tags, well set up a couple of helper functions. The first function will help us create tag attributes.
Formatting attributes
APL uses a high minus (¯
), to indicate that a number is negative. This avoids confusion with the negate function -
. However, SVG uses a regular dash, so we need to change our numeric arrays into character representations (using the format function, ⍕
), and replace all occurrences of the symbol:
'¯'⎕R'-'⍕3 ¯1 2 ¯7 3 -1 2 -7
The attribute value needs to be quoted, so we prepend (,
) two quotation marks, and then we rotate the text one step (left), thereby pushing the first one to the end:
1⌽'""','¯'⎕R'-'⍕3 ¯1 2 ¯7 "3 -1 2 -7"
Finally, create the full attribute phrase:
' ','test','=',1⌽'""','¯'⎕R'-'⍕3 ¯1 2 ¯7 test="3 -1 2 -7"
Our first function
In the most basic form, a dfn ("dee fun") is just an expression in curly braces with ⍺
and ⍵
representing the left and right arguments, just like they are the leftmost and rightmost letters of the wikipedia:Greek alphabet:
Attr←{' ',⍺,'=',1⌽'""','¯'⎕R'-'⍕⍵} 'test' Attr 3 ¯1 2 ¯7 test="3 -1 2 -7"
Notice that assignment works for functions too! Remember the automatic mapping? It doesn't apply to user-defined functions, but we can use the Each operator (¨
) instead:
'test' 'foo' Attr¨ 3 ¯1 ┌─────────┬─────────┐ │ test="3"│ foo="-1"│ └─────────┴─────────┘
Flattening enclosed things and enclosing flat things
Next, we make this list of strings (each of which is actually just a character list) into a simple list with the enlist function (∊
), and use above same concatenation and rotation techniques to finalise our tag:
2⌽'/><tag',∊'test' 'foo'Attr¨ 3 ¯1 <tag test="3" foo="-1"/>
Let's create a dfn for that too:
Circle←{⊂2⌽'/><circle',∊'cx' 'cy' 'r'Attr¨⍵} Circle 3 1 4 ┌─────────────────────────────┐ │<circle cx="3" cy="1" r="4"/>│ └─────────────────────────────┘
Notice that a put in ⊂
) which encloses the result. This is because I want to deal with these tags a scalar elements. Now we can create our circles (and show the first three:
3↑circles←,Circle⍤1⊢xy,r ┌─────────────────────────────┬─────────────────────────────┬──────────────────────────────┐ │<circle cx="0" cy="0" r="1"/>│<circle cx="0" cy="9" r="2"/>│<circle cx="0" cy="21" r="3"/>│ └─────────────────────────────┴─────────────────────────────┴──────────────────────────────┘
Here we're using ⍤
again, but this time only with a single argument, so we only specify one rank — the rank of the sub-arrays we want Circle
called on. Rows are vector (list), so that's rank 1.
Monadic ,
is called Ravel as it unravels an array into a vector, but unlike ∊
it doesn't flatten enclosed elements.
The <svg>
container
APL Wiki is a MediaWiki which has strict requirements on the dimensions of the site logo:
⊢dims←∊'width' 'height'Attr¨130 width="130" height="130"
Since the circles on the first row and in the first column are at position 0, we need our SVG to begin a bit further to the top left. Similarly, it needs to end a bit further to the bottom right than the last circle. How much? Well, the maximum radius (which would extend up and to the left) in the first row (and column) is:
⌈/1↑r 3
But let's add a bit more so the circles don't touch the image edge:
⊢pad←2+⌈/1↑r 5
Taking things to a higher dimension
Finding the centres of the first and last circles with Circle
but this time with a 2-element left argument (this takes the first 1 layer and the first 1 row of that, etc.), we can find out where to begin, and the size of our image, which makes up the "viewBox" attribute:
⊢first←1 1↑locs 0 0 ⊢last←¯1 ¯1↑locs 42 42 ⊢begin←first-pad ¯5 ¯5 ⊢size←(last-first)+2×pad 52 52 ⊢viewBox←'viewBox'Attr,begin,size viewBox="-5 -5 52 52"
The extra ravel ,
is because begin
and size
are matrices, while Attr
needs a vector.
Now we construct the opening tag:
⊢svg←'<svg',dims,viewBox,' xmlns="http://www.w3.org/2000/svg">' <svg width="130" height="130" viewBox="-5 -5 52 52" xmlns="http://www.w3.org/2000/svg">
In-place modification through assignment
We can do in-place concatenations to add all the circle tags and the closing tag:
svg,←∊circles 100↑svg,←∊circles <circle cx="0" cy="0" r="1"/><circle cx="0" cy="9" r="2"/><circle cx="0" cy="21" r="3"/><circle cx=" svg,←'</svg>'
You may recognise the pattern here as wikipedia:augmented assignment from C and related programming languages. APL allows you to use any function to modify values in-place. If you are not familiar with this, then just think of name,←value
as name←name,value
(but the pass-though value is whatever is on the right of the assignment arrow).
</source>
Doing something with what we made
Now we we can put the SVG data into a native (OS) file:
⊢svg ⎕NPUT '/tmp/aplwiki.svg' 850
The result is the number of bytes written (which can vary due to line ending standards). Alternatively, flatten the svg and we can show it in an embedded HTML window:
]html svg
Code
Logo←{ r←⌊∘.+⍨0.5×4!⍨⍳5 offsets←0,+\1+⌈⌿2+/sizes indices←⍳⍴r xy←indices⌷⍤0 1⊢offsets Attr←{' ',⍺,'=',1⌽'""','¯'⎕R'-'⍕⍵} Circle←{⊂2⌽'/><circle',∊'cx' 'cy' 'r'Attr¨⍵} circles←,Circle⍤1⊢xy,r dims←∊'width' 'height'Attr¨130 pad←2+⌈/0⌷sizes first←1 1↑locs last←¯1 ¯1↑locs begin←first-pad size←(last-first)+2×pad viewBox←'viewBox'Attr,begin,size svg←⊂'<svg',dims,viewBox,' xmlns="http://www.w3.org/2000/svg">' svg,←circles,⊂'</svg>' svg ⎕NPUT ⍵ }
References
- ↑ "Bubbler", message "52389201" in The Nineteenth Byte chat room. Stack Exchange network, 2019-10-31 23:57
- ↑ K.E. Iverson, Appendix A: Conventions Governing Order of Evaluation, Elementary Functions: An Algorithmic Treatment). Science Research Associates, 1966