APL on Irregularly Scheduled Programming
https://jcarroll.com.au/tags/apl/
Recent content in APL on Irregularly Scheduled ProgrammingHugo -- gohugo.ioenwebsite@jcarroll.com.au (Jonathan Carroll)website@jcarroll.com.au (Jonathan Carroll)Wed, 29 May 2024 00:00:00 +0000Iterative Square Root
https://jcarroll.com.au/2024/05/29/iterative-square-root/
Wed, 29 May 2024 00:00:00 +0000website@jcarroll.com.au (Jonathan Carroll)https://jcarroll.com.au/2024/05/29/iterative-square-root/<p>I saw a toot celebrating a short, clean implementation of a square root
finding algorithm and wanted to dig a bit deeper into how it works, with a
diversion into some APL.</p>
<p><a href="https://hachyderm.io/@jimgar/112521236754983554">This</a> was the toot from Jim
Gardner</p>
<blockquote>
<p>Doubly pleased with myself.</p>
<p>Been doing the Tour of Go. Got to the section where you make a square root function, which should return once the calculated value stops changing. Struggled for ages. Trimmed and trimmed. Until finally… this!</p>
<p>The calculation for z was given, and I don’t understand it at all. But I don’t care. It was a total mess when I started and has turned out quite neat. I’m very satisfied.</p>
<p>But why “doubly pleased”? Because I’ve been solely using Neovim so far for Go!!</p>
</blockquote>
<pre class="r"><code>package main
import (
"fmt"
)
func Sqrt(x float64) float64 {
z := 1.0
for {
y := z
z -= (z*z - x) / (2 * z)
if z == y {
return z
}
}
}
func main() {
fmt.Println(Sqrt(16))
} </code></pre>
<p>It’s a nice, not-too-complicated algorithm to play with, and I agree it’s hard to
see <em>why</em> it works for this application, so I thought it would be neat to walk
through that.</p>
<p>What we’re trying to solve here is the function <span class="math inline">\(y = x^2\)</span> which we could write as
<span class="math inline">\(f(x) = x^2 - y\)</span> for which we want the value <span class="math inline">\(x\)</span> where <span class="math inline">\(f(x) = 0\)</span>.</p>
<p><a href="https://en.wikipedia.org/wiki/Newton%27s_method">Newton’s Method</a> is an iterative
method for solving equations of this type (not all equations, mind you - I have an
entire chapter of my PhD thesis discussing exactly <em>why</em> it can’t be used to
solve the equations I was solving that my supervisor insisted it could). It works
by using the slope (derivative) at a point to guide towards a solution. The formula
for the updated value <span class="math inline">\(x_{n+1}\)</span> given some guess <span class="math inline">\(x_n\)</span> is</p>
<p><span class="math display">\[
x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)}
\]</span>
where <span class="math inline">\(f'(x)\)</span> is the derivative of the function <span class="math inline">\(f\)</span> at the point <span class="math inline">\(x\)</span>. For <span class="math inline">\(f(x) = x^2 - y\)</span>
the derivative is <span class="math inline">\(f'(x) = 2x\)</span> so we can substitute this and <span class="math inline">\(f(x)\)</span> into the above formula</p>
<p><span class="math display">\[
x_{n+1}=x_n−\frac{x_n^2-y}{2x_n}
\]</span>
This is what the Go code calculates; given an initial guess of <span class="math inline">\(x_n = 1\)</span> it calculates the next value as</p>
<pre><code>x = x - (x*x - y) / (2 * x) </code></pre>
<p>where, here, <code>y</code> is the value we’re finding the square root of.</p>
<p>In R this could be written as</p>
<pre class="r"><code>SQRT <- function(x) {
z <- 1
while (TRUE) {
y <- z
z <- z - (z*z - x)/(2*z)
if (abs(y -z) < 0.0001) return(z)
}
}</code></pre>
<p>(since <code>base::sqrt</code> is already defined) where I’ve used a tolerance rather than
relying on exact numerical equality. The <code>while(TRUE)</code> construct is equivalent
to Go’s <code>for {}</code> syntax; an infinite loop.</p>
<p>R actually has another way to write that which is even closer; <code>repeat {}</code></p>
<pre class="r"><code>SQRT <- function(x) {
z <- 1
repeat {
y <- z
z <- z - (z*z - x)/(2*z)
if (abs(y -z) < 0.0001) return(z)
}
}</code></pre>
<p>One might notice that this approach requires essentially squaring a value, which
is hardly expensive, but we can simplify and cancel out <span class="math inline">\(x_n\)</span>, so</p>
<p><span class="math display">\[
x_{n+1}=\frac{x_n-\frac{y}{x_n}}{2}
\]</span>
in which case we have</p>
<pre class="r"><code>SQRT <- function(x) {
z <- 1
repeat {
y <- z
z <- (z + x/z)/2
if (abs(y -z) < 0.0001) return(z)
}
}</code></pre>
<p>One of the reasons I wanted to dig into this was the fact that it’s a convergence…</p>
<p>In APL the power operator (<code>⍣</code> <a href="https://aplwiki.com/wiki/Power_(operator)">aplwi</a>)
applies a function some specified number of times, so</p>
<pre class="apl"><code> f ⍣n x</code></pre>
<p>applies <code>f</code> to <code>x</code> <code>n</code> times, i.e. <code>(f⍣3)x</code> produces <code>f(f(f(x)))</code>.</p>
<p>It can also be used as <code>⍣=</code> where it will continue to apply the function until
the output no longer changes (is equal). A classic example is the
<a href="https://en.wikipedia.org/wiki/Golden_ratio">golden ratio</a>; take the reciprocal
then add 1 until it converges, i.e. </p>
<p><span class="math display">\[
x_{n+1} = 1+\frac{1}{x_n}
\]</span></p>
<p>which you can try for yourself
<a href="https://tryapl.org/?clear&q=1%2B%E2%88%98%C3%B7%E2%8D%A3%3D1&run">here</a></p>
<pre class="apl"><code> 1+∘÷⍣=1
1.618033989</code></pre>
<p>In this, <code>+∘÷</code> is the (<a href="https://aplwiki.com/wiki/Tacit">tacit</a>) function created by
composing (<code>∘</code>) ‘addition of 1’ (<code>1+</code>, a partial application of a function) and
‘reciprocal’ (<code>÷</code>), which is iterated until it no longer changes (within
machine precision).</p>
<p>Iterating until convergence is exactly what we want, since we’re looking for the
value satisfying</p>
<p><span class="math display">\[
x_n = x_{n+1}=\frac{x_n-\frac{y}{x_n}}{2}
\]</span>
APL uses <code>⍵</code> as the right argument placeholder and <code>⍺</code> as the left, so the
function we want to apply repeatedly to the right argument is</p>
<pre class="apl"><code> {⍵-(((⍵×⍵)-⍺)÷(2×⍵))}</code></pre>
<p>If we provide <code>1</code> as the right argument (the start value) and <code>16</code> as the left
argument, we get</p>
<pre class="apl"><code> 16{⍵-(((⍵×⍵)-⍺)÷(2×⍵))}⍣=1
4</code></pre>
<p>You can try this out yourself at <a href="https://tryapl.org/?clear&q=16%7B%E2%8D%B5-(((%E2%8D%B5%C3%97%E2%8D%B5)-%E2%8D%BA)%C3%B7(2%C3%97%E2%8D%B5))%7D%E2%8D%A3%3D1&run">tryapl.org</a>
(link should load that expression).</p>
<p>We can turn that into a function, once again using the argument placeholder</p>
<pre class="apl"><code> sqrt←{⍵{⍵-(((⍵×⍵)-⍺)÷(2×⍵))}⍣=1}
sqrt 25
5
sqrt 81
9</code></pre>
<p>Taking the simplification above, we can write this a bit shorter as</p>
<pre class="apl"><code> sqrt←{⍵{(⍵+(⍺÷⍵))÷2}⍣=1}
sqrt 144
12</code></pre>
<p>As clean as the Go code looks, I think there’s a certain beauty to being able
to write this in just 20 characters. It’s not for everyone, I get that.</p>
<p>I love these opportunities to learn a bit more about languages.</p>
<p>If you have comments, suggestions, or improvements, as always, feel free to use
the comment section below, or hit me up on
<a href="https://fosstodon.org/@jonocarroll">Mastodon</a>.</p>
<br />
<details>
<summary>
<tt>devtools::session_info()</tt>
</summary>
<pre class="bg-success"><code>## ─ Session info ───────────────────────────────────────────────────────────────
## setting value
## version R version 4.3.3 (2024-02-29)
## os Pop!_OS 22.04 LTS
## system x86_64, linux-gnu
## ui X11
## language (EN)
## collate en_AU.UTF-8
## ctype en_AU.UTF-8
## tz Australia/Adelaide
## date 2024-05-29
## pandoc 3.1.11 @ /usr/lib/rstudio/resources/app/bin/quarto/bin/tools/x86_64/ (via rmarkdown)
##
## ─ Packages ───────────────────────────────────────────────────────────────────
## package * version date (UTC) lib source
## blogdown 1.18 2023-06-19 [1] CRAN (R 4.3.2)
## bookdown 0.36 2023-10-16 [1] CRAN (R 4.3.2)
## bslib 0.6.1 2023-11-28 [3] CRAN (R 4.3.2)
## cachem 1.0.8 2023-05-01 [3] CRAN (R 4.3.0)
## callr 3.7.3 2022-11-02 [3] CRAN (R 4.2.2)
## cli 3.6.2 2023-12-11 [3] CRAN (R 4.3.2)
## crayon 1.5.2 2022-09-29 [3] CRAN (R 4.2.1)
## devtools 2.4.5 2022-10-11 [1] CRAN (R 4.3.2)
## digest 0.6.34 2024-01-11 [3] CRAN (R 4.3.2)
## ellipsis 0.3.2 2021-04-29 [3] CRAN (R 4.1.1)
## evaluate 0.23 2023-11-01 [3] CRAN (R 4.3.2)
## fastmap 1.1.1 2023-02-24 [3] CRAN (R 4.2.2)
## fs 1.6.3 2023-07-20 [3] CRAN (R 4.3.1)
## glue 1.7.0 2024-01-09 [3] CRAN (R 4.3.2)
## htmltools 0.5.7 2023-11-03 [3] CRAN (R 4.3.2)
## htmlwidgets 1.6.2 2023-03-17 [1] CRAN (R 4.3.2)
## httpuv 1.6.12 2023-10-23 [1] CRAN (R 4.3.2)
## icecream 0.2.1 2023-09-27 [1] CRAN (R 4.3.2)
## jquerylib 0.1.4 2021-04-26 [3] CRAN (R 4.1.2)
## jsonlite 1.8.8 2023-12-04 [3] CRAN (R 4.3.2)
## knitr 1.45 2023-10-30 [3] CRAN (R 4.3.2)
## later 1.3.1 2023-05-02 [1] CRAN (R 4.3.2)
## lifecycle 1.0.4 2023-11-07 [3] CRAN (R 4.3.2)
## magrittr 2.0.3 2022-03-30 [3] CRAN (R 4.2.0)
## memoise 2.0.1 2021-11-26 [3] CRAN (R 4.2.0)
## mime 0.12 2021-09-28 [3] CRAN (R 4.2.0)
## miniUI 0.1.1.1 2018-05-18 [1] CRAN (R 4.3.2)
## pkgbuild 1.4.2 2023-06-26 [1] CRAN (R 4.3.2)
## pkgload 1.3.3 2023-09-22 [1] CRAN (R 4.3.2)
## prettyunits 1.2.0 2023-09-24 [3] CRAN (R 4.3.1)
## processx 3.8.3 2023-12-10 [3] CRAN (R 4.3.2)
## profvis 0.3.8 2023-05-02 [1] CRAN (R 4.3.2)
## promises 1.2.1 2023-08-10 [1] CRAN (R 4.3.2)
## ps 1.7.6 2024-01-18 [3] CRAN (R 4.3.2)
## purrr 1.0.2 2023-08-10 [3] CRAN (R 4.3.1)
## R6 2.5.1 2021-08-19 [3] CRAN (R 4.2.0)
## Rcpp 1.0.11 2023-07-06 [1] CRAN (R 4.3.2)
## remotes 2.4.2.1 2023-07-18 [1] CRAN (R 4.3.2)
## rlang 1.1.3 2024-01-10 [3] CRAN (R 4.3.2)
## rmarkdown 2.25 2023-09-18 [3] CRAN (R 4.3.1)
## rstudioapi 0.15.0 2023-07-07 [3] CRAN (R 4.3.1)
## sass 0.4.8 2023-12-06 [3] CRAN (R 4.3.2)
## sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.3.2)
## shiny 1.7.5.1 2023-10-14 [1] CRAN (R 4.3.2)
## stringi 1.8.3 2023-12-11 [3] CRAN (R 4.3.2)
## stringr 1.5.1 2023-11-14 [3] CRAN (R 4.3.2)
## urlchecker 1.0.1 2021-11-30 [1] CRAN (R 4.3.2)
## usethis 2.2.2 2023-07-06 [1] CRAN (R 4.3.2)
## vctrs 0.6.5 2023-12-01 [3] CRAN (R 4.3.2)
## xfun 0.41 2023-11-01 [3] CRAN (R 4.3.2)
## xtable 1.8-4 2019-04-21 [1] CRAN (R 4.3.2)
## yaml 2.3.8 2023-12-11 [3] CRAN (R 4.3.2)
##
## [1] /home/jono/R/x86_64-pc-linux-gnu-library/4.3
## [2] /usr/local/lib/R/site-library
## [3] /usr/lib/R/site-library
## [4] /usr/lib/R/library
##
## ──────────────────────────────────────────────────────────────────────────────</code></pre>
</details>
<p><br /></p>
Advent of Array Elegance (AoC2023 Day 7)
https://jcarroll.com.au/2023/12/10/advent-of-array-elegance/
Sun, 10 Dec 2023 00:00:00 +0000website@jcarroll.com.au (Jonathan Carroll)https://jcarroll.com.au/2023/12/10/advent-of-array-elegance/<p>I’m solving <a href="https://adventofcode.com/">Advent of Code</a> this year using a
relaxed criteria compared to <a href="https://jcarroll.com.au/2023/11/28/advent-of-code-2022/">last
year</a> in that I’m
allowing myself to use packages where they’re helpful, rather than <em>strictly</em>
base R. Last year I re-solved half of the exercises using Rust which helped me
learn a lot about Rust. This year I’m enamored with APL, and I wanted to share a
particularly beautiful solution.</p>
<p>⚠⚠⚠⚠⚠</p>
<p>Spoilers ahead for Day 7, in case you haven’t yet completed it yourself.</p>
<p>⚠⚠⚠⚠⚠</p>
<p>I solved Day 7 of Advent of Code using base R by testing whether or not a given
hand was of each type with an individual function, either returning 0 (if it was
not of that type) or <code>N</code> + a score, where <code>N</code> was sufficiently different between
each type that they would sort nicely. For the score, I initially tried
offsetting each card in a poor-man’s base-15 as <code>15^(4:0)*card_score</code> but later
improved on that by using hex digits (which automatically sort nicer). The large <code>N</code>
values ensured that ‘type’ would be sorted before the first/second/etc.. card.</p>
<p>That was sufficient to do an <code>apply(strength, hands)</code>, calculate the <code>order</code> of
those, and multiply by the relevant bids. Aside from a bug not caught by the
test case (the difference between <code>bid*order(x)</code> and
<code>bid[order(x)]*seq_along(x)</code>) it was an okay solution to the problem, and it
worked.</p>
<p>After solving each day, I’ve been trying to re-solve using APL; in particular Dyalog
APL. For those who don’t know, APL is an old language (circa 1960s) borne from a
mathematical notation in which a single glyph (symbol) represents some operation
or application of a function. This makes it look very different to more modern
languages, partly because of the glyphs, but also because it requires no boilerplate
whatsoever. As an array language, it deals with vectors and matrices without needing
to “loop over columns” or “for i in values”. It looks scary at first, but it’s really
not - once you’re familiar with the glyphs it’s actually beautiful!</p>
<p>Let’s say you have a matrix <code>m</code> which contains the values <code>1</code> through <code>9</code></p>
<pre><code> m
1 2 3
4 5 6
7 8 9</code></pre>
<p>and you want to sum the columns. Chances are, the language you normally use will
require you to first calculate the size of the matrix, maybe even perform a loop. In
APL, it’s</p>
<pre><code> +⌿m
12 15 18</code></pre>
<p><code>⌿</code> is the glyph for “reduce along first axis”, or perform some operation
(supplied as its left argument) to its right argument. <code>+⌿</code> is therefore “sum
columns”. No boilerplate, just a direct explanation (the glyphs themselves are
better names than any word you could attach to them) of what needs to be done.</p>
<p>Sure, you need to learn the glyphs, and potentially even how to enter them; one option
being a prefix key then a corresponding key. How committed am I to learning
those, you ask? Well, here’s my laptop</p>
<div class="float">
<img src="images/aplkeyboard_800.jpg" alt="My laptop with APL stickers on the keys" />
<div class="figcaption">My laptop with APL stickers on the keys</div>
</div>
<p>I considered using APL for my Day 7 solution, but it was so many functions
defined, and fiddly if/else logic, I figured it was just ill-suited to APL. Then
I saw a <a href="https://www.youtube.com/watch?v=C395wCEDvOQ">video recap of an APL solution for Day
7</a> and my mind was blown.</p>
<p>Meanwhile, I saw a post from <a href="https://fosstodon.org/@loke@functional.cafe">Elias
Mårtenson</a>, creator of the Kap
language, promoting some examples of Kap and was even more interested given that
it can do some things that (Dyalog) APL can’t, like produce graphics.</p>
<p>Can your APL do this?</p>
<pre class="r"><code> chart:line mtcars
┌→──────────────────────────────────────────────────────────────┐
↓1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1│
│4 4 4 3 3 3 3 4 4 4 4 3 3 3 3 3 3 4 4 4 3 3 3 3 3 4 5 5 5 5 5 4│
│4 4 1 1 2 1 4 2 2 4 4 3 3 3 4 4 4 1 2 1 1 2 2 4 2 1 2 2 4 6 8 2│
└───────────────────────────────────────────────────────────────┘</code></pre>
<div class="float">
<img src="images/kap_mtcars.png" alt="chart:line mtcars in Kap" />
<div class="figcaption">chart:line mtcars in Kap</div>
</div>
<p><a href="https://kapdemo.dhsdevelopments.com/">Kap</a> is a fairly new APL-based language
(written in Kotlin) that supports most of Dyalog APL, but adds some cool
extensions and alterations like lazy evaluation and parallel execution.</p>
<p><a href="https://www.uiua.org/">Uiua</a> is another new language on the scene (written in
Rust) which also supports graphics; the Uiua logo itself is written in Uiua</p>
<pre class="r"><code>Xy ← °⍉⊞⊟. ÷÷2: -÷2,⇡.200
Rgb ← [:°⊟×.Xy ↯△⊢Xy0.5]
u ← ↥<0.2:>0.7.+×2 ×.:°⊟Xy
c ← <:⍜°√/+ Xy
⍉⊂:-¬u c1 +0.1 ↧¤c0.95Rgb</code></pre>
<div class="float">
<img src="images/uiua.png" alt="Uiua logo, coded in Uiua" />
<div class="figcaption">Uiua logo, coded in Uiua</div>
</div>
<p>The online editor for Uiua uses colours to distinguish different
functions/operators, and the author has the flexibility to do what they want
with that, so it’s awesome to see what they’ve used for “all” (<code>⋔</code>) and
“<strong>trans</strong>pose” (<code>⍉</code>)…</p>
<div class="float">
<img src="images/uaua_all_trans.png" alt="Uiua coloured glyphs for ‘all’ and ‘transpose’" />
<div class="figcaption">Uiua coloured glyphs for ‘all’ and ‘transpose’</div>
</div>
<p>I figured I’d try to reproduce the APL solution in Kap as a way to learn more
about that language. The APL/Kap solution is so elegant! Additionally, I tried
writing equivalent R code. I’ll interleave all three in this post (a nice excuse
to get <a href="https://yihui.org/en/2023/10/section-tabsets/">tabsets</a> working!).</p>
<div id="reading-input" class="section level2">
<h2>Reading Input</h2>
<p>To start with, get the data into the workspace - this reads in a vector with
each element representing a row of input</p>
<div class="tabset">
</div>
<ul>
<li><p>APL</p>
<p>Reading from a file is performed using <code>⎕NGET</code></p>
<pre class="r"><code> ⊃⎕NGET'p07.txt'1</code></pre>
<pre class="r bg-success"><code> 32T3K 765 T55J5 684 KK677 28 KTJJT 220 QQQJA 483</code></pre></li>
<li><p>Kap</p>
<p>Kap uses some namespaces, which makes reading in a bit nicer, and the output is
boxed, with explicit quotes for strings</p>
<pre class="r"><code>p ← io:read "p07.txt"</code></pre>
<pre class="r bg-success"><code>┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃"32T3K 765" "T55J5 684" "KK677 28" "KTJJT 220" "QQQJA 483"┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛</code></pre></li>
<li><p>R</p>
<p><code>readLines</code> reads in each line as an element of a vector</p>
<pre class="r"><code>p <- readLines("example07.txt")
p</code></pre>
<pre class="bg-success"><code>## [1] "32T3K 765" "T55J5 684" "KK677 28" "KTJJT 220" "QQQJA 483"</code></pre></li>
</ul>
</div>
<div id="preprocessing" class="section level2">
<h2>Preprocessing</h2>
<p>The input consists of hands of cards juxtaposed with a bid value, separated by
a space. The approach here is not to treat them individually, but to create a
matrix containing columns of hands and columns of bids.</p>
<div class="tabset">
</div>
<ul>
<li><p>APL</p>
<p>Partition (<code>(≠⊆⊢)</code>) on spaces (<code>' '</code>) for each (<code>¨</code>) row</p>
<pre class="r"><code> ' '(≠⊆⊢)¨⊃⎕NGET'p07.txt'1</code></pre>
<pre class="r bg-success"><code> 32T3K 765 T55J5 684 KK677 28 KTJJT 220 QQQJA 483</code></pre>
<p>It’s not entirely clear from this layout, but this is a vector of length-2 vectors.</p>
<p>These are “mixed” (stacked; <code>↑</code>), and the result assigned (<code>←</code>) to <code>p</code></p>
<pre class="r"><code>p←↑' '(≠⊆⊢)¨⊃⎕NGET'p07.txt'1</code></pre>
<pre class="r bg-success"><code> 32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483 </code></pre>
<p>This is now a matrix, where the first column contains the hands, the second
(last) column contains the bids.</p></li>
<li><p>Kap</p>
<p>Rather than the partition idiom, Kap has regex support, so splitting the
components involes <code>regex:split</code> for each (<code>¨</code>) element of input</p>
<pre class="r"><code>p←⊃{" " regex:split ⍵}¨p</code></pre>
<pre class="r bg-success"><code>┌→────────────┐
↓"32T3K" "765"│
│"T55J5" "684"│
│"KK677" "28"│
│"KTJJT" "220"│
│"QQQJA" "483"│
└─────────────┘</code></pre></li>
<li><p>R</p>
<p>The boilerplate of R’s matrix construction takes a toll after using APL/Kap…</p>
<pre class="r"><code>p <- matrix(unlist(strsplit(p, " ")), ncol = 2, byrow = TRUE)
p</code></pre>
<pre class="bg-success"><code>## [,1] [,2]
## [1,] "32T3K" "765"
## [2,] "T55J5" "684"
## [3,] "KK677" "28"
## [4,] "KTJJT" "220"
## [5,] "QQQJA" "483"</code></pre></li>
</ul>
</div>
<div id="extraction" class="section level2">
<h2>Extraction</h2>
<p>The hands and bids can be extracted into their own variables.</p>
<div class="tabset">
</div>
<ul>
<li><p>APL</p>
<p>This can be achieved several ways, but a clean way is by reducing (<code>/</code>) with
either the ‘leftmost’ (<code>⊣</code>) or ‘rightmost’ (<code>⊢</code>) operator, and evaluating
(executing <code>⍎</code>) each (<code>¨</code>) of the bids to convert from strings to numbers</p>
<pre class="r"><code>hands←⊣/p
bids←⍎¨⊢/p</code></pre></li>
<li><p>Kap</p>
<p>Kap uses exactly the same approach as APL for this</p>
<pre class="r"><code>hands←⊣/p
bids←⍎¨⊢/p</code></pre></li>
<li><p>R</p>
<p>R’s ‘subset by index’ works just fine, but if this was generalised I’d use
something like <code>p[, ncol(p)]</code> to get to the last column</p>
<pre class="r"><code>hands <- p[,1]
hands</code></pre>
<pre class="bg-success"><code>## [1] "32T3K" "T55J5" "KK677" "KTJJT" "QQQJA"</code></pre>
<pre class="r"><code>bids <- as.integer(p[,2])
bids</code></pre>
<pre class="bg-success"><code>## [1] 765 684 28 220 483</code></pre></li>
</ul>
</div>
<div id="tabulate" class="section level2">
<h2>Tabulate</h2>
<p>Now comes the interesting part! Rather than deal with the types separately, one
approach is to identify them by their relative counts; a five-of-a-kind has 5 of
one card and nothing elese; a four-of-a-kind has four of one and one of another.</p>
<div class="tabset">
</div>
<ul>
<li><p>APL</p>
<p>APL has a “key” (<code>⌸</code>) which takes a function as a left argument, which can be to
count the occurrences of each element with “tally” (<code>≢</code>)</p>
<pre class="r"><code> {⍺,≢⍵}⌸'TGGATAACTTGAAC'</code></pre>
<pre class="r bg-success"><code>T 4
G 3
A 5
C 2</code></pre>
<p>In this case, we can get just the tallied count of each card in the hand with</p>
<pre class="r"><code> {⊢∘≢⍵}⌸¨hands</code></pre>
<pre class="r bg-success"><code>2 1 1 1 1 3 1 2 1 2 1 2 2 3 1 1</code></pre>
<p>We can then sort (<code>⍵[⍒⍵]</code>) these, take just the first two values (<code>2↑</code>), and
decode (<code>⊥</code>) using base 10 to a single number. A nice feature of APL is that
trying to take the “first N” elements of a single element pads to the full N
with zeroes.</p>
<pre class="r"><code> f←{10⊥2↑{⍵[⍒⍵]}⊢∘≢⌸⍵}
f¨hands</code></pre>
<pre class="r bg-success"><code>21 31 22 22 31</code></pre></li>
<li><p>Kap</p>
<p>Kap doesn’t have the equivalent Key, but after some discussion with the creator,
it’s entirely possible to get something that does the same</p>
<pre class="r"><code> key⇐(⍪+⌿≡⌻)∘∪ ⍝ using outer product - see the R solution
key2⇐{u←∪⍵ ⋄ c←⍸˝∧u⍳⍵} ⍝ using inverse 'where' and 'index of'
key2¨hands</code></pre>
<pre class="r bg-success"><code>┌→────────────────────────────────────────┐
│┌→──────┐ ┌→────┐ ┌→────┐ ┌→────┐ ┌→────┐│
││2 1 1 1│ │1 3 1│ │2 1 2│ │1 2 2│ │3 1 1││
│└───────┘ └─────┘ └─────┘ └─────┘ └─────┘│
└─────────────────────────────────────────┘</code></pre>
<p>The rest is the same as APL, except Kap uses a dedicated sort (<code>∨</code>)</p>
<pre class="r"><code> handrank⇐{10⊥2↑∨⊢/key ⍵}
handrank¨hands</code></pre>
<pre class="r bg-success"><code>┏━━━━━━━━━━━━━━┓
┃21 31 22 22 31┃
┗━━━━━━━━━━━━━━┛</code></pre></li>
<li><p>R</p>
<p>I wanted to recreate the above approach in R, so this will take the long way ’round.</p>
<p>First, we need a ‘key’ function</p>
<pre class="r"><code>key <- function(x) {
l <- strsplit(x, "")[[1]]
setNames(colSums(outer(l, unique(l), "==")), unique(l))
}
sapply(hands, key)</code></pre>
<pre class="bg-success"><code>## $`32T3K`
## 3 2 T K
## 2 1 1 1
##
## $T55J5
## T 5 J
## 1 3 1
##
## $KK677
## K 6 7
## 2 1 2
##
## $KTJJT
## K T J
## 1 2 2
##
## $QQQJA
## Q J A
## 3 1 1</code></pre>
<p>The idea of this is to create an outer product between the set of
unique letters in the string, and the individual letters, performing
an <code>==</code> check on each combination</p>
<pre class="r"><code>y <- strsplit(hands[2], "")[[1]]
outer(y, unique(y), "==")</code></pre>
<pre class="bg-success"><code>## [,1] [,2] [,3]
## [1,] TRUE FALSE FALSE
## [2,] FALSE TRUE FALSE
## [3,] FALSE TRUE FALSE
## [4,] FALSE FALSE TRUE
## [5,] FALSE TRUE FALSE</code></pre>
<p>This is, of course, unnecessary as R has a way to do this</p>
<pre class="r"><code>table(y)</code></pre>
<pre class="bg-success"><code>## y
## 5 J T
## 3 1 1</code></pre>
<p>but I wanted to see how to do it from scratch.</p>
<p>Applying this over the hands, we can sort each of the counts again, but now
taking the first two values fails for the five-of-a-kind which only has a <code>5</code>,
so in that case I add the missing 0. Decoding as base 10 can be done a couple
of ways, but pasting and converting seems to work fine.</p>
<pre class="r"><code>handrank <- function(x) {
rank <- sort(sapply(x, key), decreasing = TRUE)
if (length(rank) == 1) rank <- c(rank, 0)
as.integer(paste(rank[1:2], collapse = ""))
}
sapply(hands, handrank)</code></pre>
<pre class="bg-success"><code>## 32T3K T55J5 KK677 KTJJT QQQJA
## 21 31 22 22 31</code></pre></li>
</ul>
</div>
<div id="subsequent-rankings-and-answer" class="section level2">
<h2>Subsequent Rankings and Answer</h2>
<p>Finally, the part where the ‘array’ approach shines! Rather than constructing some
sortable number for each hand, we can just score each card and use an array.</p>
<div class="tabset">
</div>
<ul>
<li><p>APL</p>
<p>Creating a vector of all the cards is aided by the ‘numbers as a string’
helper <code>⎕D</code>. Drop the first two of these (<code>2↓</code>) then append the ‘face’ cards</p>
<pre class="r"><code> r←'TJQKA',⍨2↓⎕D
r</code></pre>
<pre class="r bg-success"><code>23456789TJQKA</code></pre>
<p>Stacking the hands into a matrix of cards</p>
<pre class="r"><code> ↑hands</code></pre>
<pre class="r bg-success"><code>32T3K
T55J5
KK677
KTJJT
QQQJA</code></pre>
<p>we can ask for the index of matches to the individual cards with <code>⍳</code></p>
<pre class="r"><code> r⍳↑hands</code></pre>
<pre class="r bg-success"><code> 2 1 9 2 12
9 4 4 10 4
12 12 5 6 6
12 9 10 10 9
11 11 11 10 13</code></pre>
<p>Prepending (<code>,</code>) each column with the tabulated type of each hand</p>
<pre class="r"><code> r{⍵,⍺⍳↑hands}f¨hands</code></pre>
<pre class="r bg-success"><code>21 2 1 9 2 12
31 9 4 4 10 4
22 12 12 5 6 6
22 12 9 10 10 9
31 11 11 11 10 13</code></pre>
<p>Now, some real magic… APL support <a href="https://aplwiki.com/wiki/Total_array_ordering">“total array
ordering”</a> which means we can just
sort the entire thing - it will sort by the first column, using the second and
subsequent columns for ties. Given that the first column is the ‘type’ of hand,
and subsequent columns are values of each card in order, that’s precisely
the sorting we need!</p>
<pre class="r"><code> r{⍋⍋⍵,⍺⍳↑hands}f¨hands</code></pre>
<pre class="r bg-success"><code>1 4 3 2 5</code></pre>
<p>There’s a nice discussion about why the double grading from
<a href="https://github.com/mlochbaum/BQN/blob/master/doc/order.md#ordinals">BQN</a></p>
<p>Finally, multiplying by the bids themselves, and sum-reducing gives the final
answer</p>
<pre class="r"><code> +/r{bids×⍋⍋⍵,⍺⍳↑hands}f¨hands</code></pre>
<pre class="r bg-success"><code>6440</code></pre></li>
<li><p>Kap</p>
<p>This is mostly the same solution as APL, except I couldn’t find the ‘numbers
as string’ so i just typed it out. Kap also uses ‘disclose’ (<code>⊃</code>) in place of
mix (<code>↑</code>)
(<a href="https://kapdemo.dhsdevelopments.com/kap-comparison.html#_enclose_and_disclose:~:text=If%20%E2%8A%83%20is%20called%20on%20an%20array%2C">ref</a>).</p>
<pre class="r"><code> ranks←"23456789TJQKA"
+/ranks{bids×1+⍋⍋⍵,⍺⍳⊃hands}handrank¨hands</code></pre>
<pre class="r bg-success"><code>6440</code></pre></li>
<li><p>R</p>
<p>R doesn’t support Total Array Ordering, but it does seem to have a way to do it,
so say the documentation examples for <code>order</code></p>
<pre class="r"><code>## or along 1st column, ties along 2nd, ... *arbitrary* no.{columns}:
dd[ do.call(order, dd), ]</code></pre>
<p>That only works for a <code>data.frame</code>, which <em>is</em> a <code>list</code> (per <code>do.call</code>’s requirement). We
can still work with that. First, smoosh together all the hands and convert the
individual cards to a matrix - again, a long line of commands for what is reasonably
straightforward in APL… <code>3 3⍴'abcdefghi'</code> reshapes those 9 letters into a 3x3 matrix.</p>
<pre class="r"><code>m <- matrix(strsplit(paste0(hands, collapse = ""), "")[[1]], ncol = 5, byrow = TRUE)
m</code></pre>
<pre class="bg-success"><code>## [,1] [,2] [,3] [,4] [,5]
## [1,] "3" "2" "T" "3" "K"
## [2,] "T" "5" "5" "J" "5"
## [3,] "K" "K" "6" "7" "7"
## [4,] "K" "T" "J" "J" "T"
## [5,] "Q" "Q" "Q" "J" "A"</code></pre>
<p>The individual cards vector benefits from coercing the digits to characters</p>
<pre class="r"><code>ranks <- c(2:9, "T", "J", "Q", "K", "A")</code></pre>
<p>The index mapping does actually work nicely with <code>match</code>, except it returns a single
vector, not a matrix, so we need to reshape yet again. Plus, this time, the matches went
down columns not along rows, so we need to use <code>byrow = FALSE</code></p>
<pre class="r"><code>mm <- matrix(match(m, ranks), ncol = 5, byrow = FALSE)
mm</code></pre>
<pre class="bg-success"><code>## [,1] [,2] [,3] [,4] [,5]
## [1,] 2 1 9 2 12
## [2,] 9 4 4 10 4
## [3,] 12 12 5 6 6
## [4,] 12 9 10 10 9
## [5,] 11 11 11 10 13</code></pre>
<p>Prepending with the type rankings does work nicely via <code>cbind</code></p>
<pre class="r"><code>g <- cbind(sapply(hands, handrank), mm)
g</code></pre>
<pre class="bg-success"><code>## [,1] [,2] [,3] [,4] [,5] [,6]
## 32T3K 21 2 1 9 2 12
## T55J5 31 9 4 4 10 4
## KK677 22 12 12 5 6 6
## KTJJT 22 12 9 10 10 9
## QQQJA 31 11 11 11 10 13</code></pre>
<p>Then ordering with the <code>do.call</code> idiom</p>
<pre class="r"><code>gdf <- as.data.frame(g)
gdf[do.call(order, gdf), ]</code></pre>
<pre class="bg-success"><code>## V1 V2 V3 V4 V5 V6
## 32T3K 21 2 1 9 2 12
## KTJJT 22 12 9 10 10 9
## KK677 22 12 12 5 6 6
## T55J5 31 9 4 4 10 4
## QQQJA 31 11 11 11 10 13</code></pre>
<p>Putting this all together into a function</p>
<pre class="r"><code>sortrank <- function(x, y) {
m <- matrix(strsplit(paste0(y, collapse = ""), "")[[1]], ncol = 5, byrow = TRUE)
mm <- matrix(match(m, x), ncol = 5, byrow = FALSE)
g <- cbind(sapply(y, handrank), mm)
do.call(order, as.data.frame(g))
}
sortrank(ranks, hands)</code></pre>
<pre class="bg-success"><code>## [1] 1 4 3 2 5</code></pre>
<p>This <em>isn’t</em> the double sorting that APL and Kap used, and that little
difference is what held me up for all too long trying to figure out why my
solution passed tests but gave the wrong answer. Annoyingly, this mistake
doesn’t show up in the test case because the ranks only differ by a switched
place. The true input was not so kind.</p>
<p>This result <em>is</em> the order in which we need to place the bids, so doing that, then
multiplying by the position (since it’s sorted, this is just a vector from <code>1</code> to the
number of elements) we get the answer</p>
<pre class="r"><code>sum(bids[sortrank(ranks, hands)]*seq_along(bids))</code></pre>
<pre class="bg-success"><code>## [1] 6440</code></pre></li>
</ul>
</div>
<div id="summary" class="section level2">
<h2>Summary</h2>
<p>So, how do these solutions all look? I’ll stop with the tabsets for a side-by-side comparison.</p>
<p>Compacting the APL solution (which does involve some duplication) it’s as simple as</p>
<pre class="r"><code>p←↑' '(≠⊆⊢)¨⊃⎕NGET'p07.txt'1
+/('TJQKA',⍨2↓⎕D){(⍎¨⊢/p)×⍋⍋⍵,⍺⍳↑⊣/p}{10⊥2↑{⍵[⍒⍵]}⊢∘≢⌸⍵}¨⊣/p</code></pre>
<p>which, admittedly, requires a fair amount of unpacking to read. In full form, it’s</p>
<pre class="r"><code>p←↑' '(≠⊆⊢)¨⊃⎕NGET'p07.txt'1
hands←⊣/p
bids←⍎¨⊢/p
f←{10⊥2↑{⍵[⍒⍵]}⊢∘≢⌸⍵}
r←'TJQKA',⍨2↓⎕D
+/r{bids×⍋⍋⍵,⍺⍳↑hands}f¨hands</code></pre>
<p>which is still pretty nice, considering what it’s doing.</p>
<p>The R solution, somewhat minimally, and leveraging <code>table</code>, is</p>
<pre class="r"><code>handrank <- function(x) {
rank <- sort(sapply(strsplit(x, ""), table), decreasing = TRUE)
if (length(rank) == 1) rank <- c(rank, 0)
as.integer(paste(rank[1:2], collapse = ""))
}
sortrank <- function(x, y) {
m <- matrix(strsplit(paste0(y, collapse = ""), "")[[1]], ncol = 5, byrow = TRUE)
mm <- matrix(match(m, x), ncol = 5, byrow = FALSE)
g <- cbind(sapply(y, handrank), mm)
do.call(order, as.data.frame(g))
}
solve <- function(x) {
p <- matrix(unlist(strsplit(x, " ")), ncol = 2, byrow = TRUE)
hands <- p[,1]
bids <- as.integer(p[,2])
ranks <- c(2:9, "T", "J", "Q", "K", "A")
sum(bids[sortrank(ranks, hands)]*seq_along(bids))
}
solve(readLines("example07.txt"))</code></pre>
<p>Certainly more typing, but still a much shorter solution than the one I originally
came up with.</p>
</div>
<div id="takeaways" class="section level2">
<h2>Takeaways</h2>
<p>Both APL and Kap (and so many other languages) benefit greatly from treating a string
as an array of characters. This always hurts in R, where <code>strsplit(x, "")</code> is needed.</p>
<p>The array approach here highlights how one can think differently about a problem, provided
the tools are at hand.</p>
<p>Kap has a lot to offer - it’s (vastly) newer, which comes with both advantages
(can do new things) and disadvantages (things need to be implemented, and they
won’t necessarily carry over 1:1).</p>
<p>Advent of Code once again proves to be a useful exercise.</p>
</div>
<div id="one-more-thing" class="section level2">
<h2>One more thing</h2>
<p>I saw <a href="https://mastodon.social/@jstepien/111549757943773283">a solution in Uiua on
Mastodon</a> and had to give
it a go, too…</p>
<pre class="r"><code>Input ← ⊜(⊜□≠@ .)≠@\n.&fras"p07.txt"
Label ← ⇌"AKQJT98765432"
Bids ← ⋕⊢↘1⍉
Cards ← ⊐≡(⊗:Label)⊢⍉
Types ← 0_1_2_4_8_5_10_9_3_6_12_11_13_7_14_15⊚1_4_3_3_2_2_1
TypeStr ← ⊏⊗⊙Types≡(°⋯≡/=◫2⊏⍏.)
/+×+1⍏⍏/+×ⁿ⇌⇡6⧻Label⊂⊃TypeStr⍉⊃Cards Bids Input</code></pre>
<p>I <em>think</em> this is taking the same approach, though unpacking this is even
trickier.</p>
<hr>
<p>Comments and improvements most welcome. I can be found on
<a href="https://fosstodon.org/@jonocarroll">Mastodon</a> or use the comments below.</p>
<br />
<details>
<summary>
<tt>devtools::session_info()</tt>
</summary>
<pre class="bg-success"><code>## ─ Session info ───────────────────────────────────────────────────────────────
## setting value
## version R version 4.3.2 (2023-10-31)
## os Pop!_OS 22.04 LTS
## system x86_64, linux-gnu
## ui X11
## language (EN)
## collate en_AU.UTF-8
## ctype en_AU.UTF-8
## tz Australia/Adelaide
## date 2023-12-10
## pandoc 3.1.1 @ /usr/lib/rstudio/resources/app/bin/quarto/bin/tools/ (via rmarkdown)
##
## ─ Packages ───────────────────────────────────────────────────────────────────
## package * version date (UTC) lib source
## blogdown 1.18 2023-06-19 [1] CRAN (R 4.3.2)
## bookdown 0.36 2023-10-16 [1] CRAN (R 4.3.2)
## bslib 0.5.1 2023-08-11 [3] CRAN (R 4.3.1)
## cachem 1.0.8 2023-05-01 [3] CRAN (R 4.3.0)
## callr 3.7.3 2022-11-02 [3] CRAN (R 4.2.2)
## cli 3.6.1 2023-03-23 [3] CRAN (R 4.2.3)
## crayon 1.5.2 2022-09-29 [3] CRAN (R 4.2.1)
## devtools 2.4.5 2022-10-11 [1] CRAN (R 4.3.2)
## digest 0.6.33 2023-07-07 [3] CRAN (R 4.3.1)
## ellipsis 0.3.2 2021-04-29 [3] CRAN (R 4.1.1)
## evaluate 0.22 2023-09-29 [3] CRAN (R 4.3.1)
## fastmap 1.1.1 2023-02-24 [3] CRAN (R 4.2.2)
## fs 1.6.3 2023-07-20 [3] CRAN (R 4.3.1)
## glue 1.6.2 2022-02-24 [3] CRAN (R 4.2.0)
## htmltools 0.5.6.1 2023-10-06 [3] CRAN (R 4.3.1)
## htmlwidgets 1.6.2 2023-03-17 [1] CRAN (R 4.3.2)
## httpuv 1.6.12 2023-10-23 [1] CRAN (R 4.3.2)
## icecream 0.2.1 2023-09-27 [1] CRAN (R 4.3.2)
## jquerylib 0.1.4 2021-04-26 [3] CRAN (R 4.1.2)
## jsonlite 1.8.7 2023-06-29 [3] CRAN (R 4.3.1)
## knitr 1.44 2023-09-11 [3] CRAN (R 4.3.1)
## later 1.3.1 2023-05-02 [1] CRAN (R 4.3.2)
## lifecycle 1.0.3 2022-10-07 [3] CRAN (R 4.2.1)
## magrittr 2.0.3 2022-03-30 [3] CRAN (R 4.2.0)
## memoise 2.0.1 2021-11-26 [3] CRAN (R 4.2.0)
## mime 0.12 2021-09-28 [3] CRAN (R 4.2.0)
## miniUI 0.1.1.1 2018-05-18 [1] CRAN (R 4.3.2)
## pkgbuild 1.4.2 2023-06-26 [1] CRAN (R 4.3.2)
## pkgload 1.3.3 2023-09-22 [1] CRAN (R 4.3.2)
## prettyunits 1.2.0 2023-09-24 [3] CRAN (R 4.3.1)
## processx 3.8.2 2023-06-30 [3] CRAN (R 4.3.1)
## profvis 0.3.8 2023-05-02 [1] CRAN (R 4.3.2)
## promises 1.2.1 2023-08-10 [1] CRAN (R 4.3.2)
## ps 1.7.5 2023-04-18 [3] CRAN (R 4.3.0)
## purrr 1.0.2 2023-08-10 [3] CRAN (R 4.3.1)
## R6 2.5.1 2021-08-19 [3] CRAN (R 4.2.0)
## Rcpp 1.0.11 2023-07-06 [1] CRAN (R 4.3.2)
## remotes 2.4.2.1 2023-07-18 [1] CRAN (R 4.3.2)
## rlang 1.1.1 2023-04-28 [3] CRAN (R 4.3.0)
## rmarkdown 2.25 2023-09-18 [3] CRAN (R 4.3.1)
## rstudioapi 0.15.0 2023-07-07 [3] CRAN (R 4.3.1)
## sass 0.4.7 2023-07-15 [3] CRAN (R 4.3.1)
## sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.3.2)
## shiny 1.7.5.1 2023-10-14 [1] CRAN (R 4.3.2)
## stringi 1.7.12 2023-01-11 [3] CRAN (R 4.2.2)
## stringr 1.5.0 2022-12-02 [3] CRAN (R 4.3.0)
## urlchecker 1.0.1 2021-11-30 [1] CRAN (R 4.3.2)
## usethis 2.2.2 2023-07-06 [1] CRAN (R 4.3.2)
## vctrs 0.6.4 2023-10-12 [3] CRAN (R 4.3.1)
## xfun 0.40 2023-08-09 [3] CRAN (R 4.3.1)
## xtable 1.8-4 2019-04-21 [1] CRAN (R 4.3.2)
## yaml 2.3.7 2023-01-23 [3] CRAN (R 4.2.2)
##
## [1] /home/jono/R/x86_64-pc-linux-gnu-library/4.3
## [2] /usr/local/lib/R/site-library
## [3] /usr/lib/R/site-library
## [4] /usr/lib/R/library
##
## ──────────────────────────────────────────────────────────────────────────────</code></pre>
</details>
<p><br /></p>
</div>
Hooray, Array!
https://jcarroll.com.au/2023/10/09/hooray-array/
Mon, 09 Oct 2023 00:00:00 +0000website@jcarroll.com.au (Jonathan Carroll)https://jcarroll.com.au/2023/10/09/hooray-array/<p>If you’re reading this hoping that I’m done with droning on about
array-languages, close the tab… it only gets worse from here. If you thought
APL was unreadable, even after my <a href="https://jcarroll.com.au/2023/07/07/array-languages-r-vs-apl/">earlier blog posts</a>, again -
close button is right there. In this post I try out a brand new <em>stack-based</em>
array language and continue to advocate for learning such things.</p>
<p>I subscribe to a lot of RSS feeds these days - it’s certainly making a comeback, and it’s
great to see developers returning to blogging outside of paid platforms. Keeping up with
all of those posts, however, does take quite a bit of time. So when I find one I do
really find engaging, I do my best to dig in.</p>
<p><a href="https://buttondown.email/hillelwayne/archive/picat-is-my-favorite-new-toolbox-language/">This post</a>
by Hillel Wayne wasn’t specifically interesting (my dance card for learning new languages is already
pretty full, but I can’t help looking at others) but it did present a small, bite-sized
puzzle to solve; what’s a simple way to “generate 5 random 10-character strings”. Now, that’s
pretty much a code-golf question right there. Hillel presents a solution in <a href="https://raku.org/">Raku</a> (a.k.a. Perl 6 - note the “p” and “6” in the Raku logo) as a “quick” solution</p>
<pre><code>for ^5 {say ('a'..'z').roll(10).join}</code></pre>
<pre class="r bg-success"><code>dmjfpwxspu
vernmlljkw
korntotesp
rkpewsoqjn
blswvruden</code></pre>
<p>and I can’t argue with that - it’s readable (even though I don’t know much Perl/Raku), I can
reason about what and how it’s doing what it’s doing (making an educated guess about <code>^</code> which
is indeed a <a href="https://docs.raku.org/language/operators#prefix_%5E">range operation</a> and <code>say</code> being
an <a href="https://docs.raku.org/routine/say">output method</a>; <code>roll</code> is a nice choice for <a href="https://docs.raku.org/routine/roll">random selection</a>).</p>
<p>When I see problems like this, I start to think through what tools I could use to solve it. I
still default to R because it’s the language I know best, but my first attempt isn’t nearly
as concise</p>
<pre class="r"><code>sapply(1:5, \(x) paste0(sample(letters, 10, replace = TRUE), collapse = ""))</code></pre>
<pre class="bg-success"><code>## [1] "kcqicytylm" "peybjcbumk" "bbvhibgqjs" "uzbpzrkywp" "zettlmjghm"</code></pre>
<p>I <em>do</em> like that R has <code>letters</code> (and <code>LETTERS</code>) as built-in structures; that makes things a
little easier. I <em>could</em> write that just as easily as a for-loop, especially since I don’t
actually need an argument to the anonymous function</p>
<pre class="r"><code>for (i in 1:5) {
print(
paste0(
sample(letters, 10, replace = TRUE),
collapse = ""
)
)
}</code></pre>
<pre class="bg-success"><code>## [1] "zwwpihqipr"
## [1] "cxunwvojaq"
## [1] "xlkcubjysw"
## [1] "ilpohtgcag"
## [1] "ralzlrszen"</code></pre>
<p>Side-by-side, these aren’t all that different…</p>
<div class="float">
<img src="images/raku_r_coloured.png" alt="Raku vs R solutions with common colouring" />
<div class="figcaption">Raku vs R solutions with common colouring</div>
</div>
<p>R defaults to <code>replace = FALSE</code> which needs adjusting, and doesn’t like concatenating
strings quite so easily as <code>join()</code>, but otherwise the translation is fairly
straightforward. The Raku version is shorter, for sure.</p>
<p>I could probably go and try a few other languages, but I’m all too tempted to try APL.
Unfortunately, <a href="https://tryapl.org/">tryapl.org</a> seems to be down, but then I remembered…
New on the scene is <a href="https://www.uiua.org/">Uiua</a> (pronounced “wee-wuh”) following the
footsteps of other APL-descendants such as <a href="https://mlochbaum.github.io/BQN/">BQN</a>. This
was covered by <a href="https://www.arraycast.com/episodes/episode63-uiua">The Array Cast panel</a>
who interviewed the author, as well as Conor from the same group in
<a href="https://www.youtube.com/watch?v=iTC1EiX5bM0">several</a> <a href="https://www.youtube.com/watch?v=pq1k5USZZ9A">videos</a>.</p>
<p>The idea of a stack-based language is that you put some data “on the stack” then
you no longer need to refer to arguments; a monadic function just applies to whatever
is on the top of the stack. A dyadic function just applies to to the top two
pieces of data on the stack. Need another copy of your data somewhere in your processing? Just
duplicate it on the stack.</p>
<p>The way this works in practice is you “read” from right-to-left (same as APL), so if we
put the values <code>2</code> and <code>3</code> on the stack then use the dyadic <code>+</code> function</p>
<pre><code>+ 3 2</code></pre>
<pre class="r bg-success"><code>5</code></pre>
<p>Similar to APL, this language uses glyphs for primitive functions, but a really nice
feature is writing out the “name” of the function you want (in the above case, <code>add</code>) which
the interpreter will convert to the glyph for you, so</p>
<pre><code>add 3 2</code></pre>
<pre class="r bg-success"><code>5</code></pre>
<p>produces the same code (with glyphs) and output.</p>
<p>Working with a stack would certainly be something different for me, but I figured it’s
worth a try! The first hurdle came quickly; how do I get the letters of the alphabet?
Reading through the examples, I found that I can specify a string literal with <code>@</code>, and
Uiua supports some <a href="https://www.uiua.org/docs/types#:~:text=Character%20Arithmetic">arithmetic on these</a> so this works</p>
<pre><code>+ @a 1
+ @a 25</code></pre>
<pre class="r bg-success"><code>@b
@z</code></pre>
<p>Next, I needed to generate all the letters, and thankfully, adding a <em>range</em> from 1 to 25
(<code>⇡26</code>) works</p>
<pre><code>+@a⇡26</code></pre>
<pre class="r bg-success"><code>"abcdefghijklmnopqrstuvwxyz"</code></pre>
<p>Note that you can also use <code>add @a range 26</code> - the interpreter inserts the glyphs for you.</p>
<p>Next, I need a way to sample 10 letters from this. There’s a <code>rand</code> function
(the glyph looks like a dice - nice!) but it only produces a single value
between 0 and 1. Additionally, I need to run this several times to get the 10
values. The front page of the website has a nice example demonstrating exactly
this, so that helps. It uses <code>rand</code> (<code>⚂</code>) and <code>repeat</code> (<code>⍥</code>) to generate 5
random numbers between 0 and 1, then <code>mult</code> (<code>×</code>) to bring the range up to 0 to
10, then finally <code>floor</code> (<code>⌊</code>) to return to integers.</p>
<pre><code>⌊×10[⍥⚂5]</code></pre>
<pre class="r bg-success"><code>[5 3 7 8 4]</code></pre>
<p>In my case, I want to generate 10 values and I need to multiply by 26 to have the
right indices</p>
<pre><code>⌊×26[⍥⚂10]+@a⇡26</code></pre>
<pre class="r bg-success"><code>"abcdefghijklmnopqrstuvwxyz"
[10 25 23 20 4 25 15 2 24 24]</code></pre>
<p>The values on the stack are then the letters of the alphabet, and 10 indices to be selected.</p>
<p>This is where I had to pause and think - how do I repeat this 5 times? There’s no loops (I
don’t think). Then I realised - this is an <em>array</em> language… I should be leveraging that!</p>
<p>Instead of asking for 10 indices, I can ask for 50. Then, I just need to <code>reshape</code> (<code>↯</code>)
these 50 values into 5 groups of 10</p>
<pre><code>↯5_10⌊×26[⍥⚂50]+@a⇡26</code></pre>
<pre class="r bg-success"><code>"abcdefghijklmnopqrstuvwxyz"
╭─
╷ 21 19 4 18 2 24 6 1 2 6
0 12 1 1 12 2 12 7 12 0
5 1 19 6 22 19 23 18 12 25
20 13 10 19 17 2 12 1 16 4
9 24 6 9 18 6 21 18 23 1
╯</code></pre>
<p>Now, there are two data objects on the stack; the alphabet, and an array of indices to be
selected. A dyadic <code>select</code> (<code>⊏</code>) will take these two objects, and select elements of the
first based on indices of the second, and voila!</p>
<pre><code>⊏↯5_10⌊×26[⍥⚂50]+@a⇡26</code></pre>
<pre class="r bg-success"><code>╭─
╷ "gewctqbttq"
"vsbvzbqiod"
"wpmkmnuxwz"
"rymyxzqibo"
"zxtnpadwvl"
╯</code></pre>
<p>That’s a walkthrough of the glyphs in my final solution - <a href="https://uiua.org/pad?src=4oqP4oavNV8xMOKMisOXMjZb4o2l4pqCNTBdK0Bh4oehMjY=">you can play
with it on the website yourself</a> - but one could enter those function names in full
and the interpreter will figure it out for you</p>
<pre><code>select reshape 5_10 floor mult 26[repeat rand 50] add @a range 26
...
⊏ ↯ 5_10 ⌊ × 26[⍥⚂ 50] + @a ⇡ 26</code></pre>
<pre class="r bg-success"><code>╭─
╷ "wtyefkiavu"
"gfllwkuqcn"
"qydoiyqprk"
"awvdxdsymj"
"zzvueychem"
╯</code></pre>
<p>I know people like to say this is “unreadable” but with a little colour, a lot of
the elements of the Raku and R solutions are here</p>
<div class="float">
<img src="images/uiua_coloured.png" alt="Uiua solution with colours corresponding to the earlier Raku and R solutions" />
<div class="figcaption">Uiua solution with colours corresponding to the earlier Raku and R solutions</div>
</div>
<p>So… is that more concise than the R or even Raku solutions? Gosh, no. BUT, I
had a lot more fun writing it. For certain problems, APL-like languages really
do have a lot to offer, and for all I know there’s a much better way to spell
this in very few glyphs that I’ve overlooked (feel free to send me one!).</p>
<p>You can make quite complex things; the Uiua logo itself - made in Uiua!</p>
<pre><code>xy ← ⍘⍉⊞⊟. ÷÷2∶ -÷2,⇡.200
Rgb ← [∶⍘⊟×.xy ↯△⊢xy0.5]
u ← ↥<0.2∶>0.7.+×2 ×.∶⍘⊟xy
c ← <∶√/+ⁿ2 xy
⍉⊂∶-¬u c1 +0.1 ∺↧Rgb c0.95</code></pre>
<div class="float">
<img src="images/uiua_logo.png" alt="Uiui code to generate the Uiua logo" />
<div class="figcaption">Uiui code to generate the Uiua logo</div>
</div>
<p>Another neat fact about this language is that it’s written in Rust, so it’s
potentially quite fast as well. Array stuff in Rust is top of mind for me at the
moment - <a href="https://www.jernesto.com/articles/rapl">this cool post</a> from earlier
in the year covers an implementation of some APL-like array processing in Rust
which I’m keen to dig deeper into (there’s a not-too-old
<a href="https://github.com/JErnestoMtz/rapl">repo</a> of things already built). I clearly
need to re-read my own posts, because I actually linked to that cool post above
in <a href="https://jcarroll.com.au/2023/07/07/array-languages-r-vs-apl/">my first APL-related post</a>, but because I had searched for “rank polymorphism” and it fit the bill.</p>
<p>The fact that R has a lot of these array-compatible functions out-of-the-box is
terribly underpromoted and undercelebrated. Bringing this back around to R, can
I use the array method there? I can certainly build a matrix of 50 letters quite
concisely, though the fact that R doesn’t concatenate characters so easily still
hurts</p>
<pre class="r"><code>m <- matrix(sample(letters, 50, replace = TRUE), 5, 10)
apply(m, 1, \(x) paste0(x, collapse = ""))</code></pre>
<pre class="bg-success"><code>## [1] "wzyfpoyegm" "xjehbspfql" "vjpvimtwkm" "uzkwmgcmix" "suakdpagvl"</code></pre>
<p>I’m hoping to play a bit more with Uiua, and I was genuinely impressed that I managed
to solve this at all, but I’m still just beginning my journey in APL and there’s no
shortage of things to learn there. In fact, despite having no tryapl.org, I <em>do</em> have
the Ride editor locally. A bit of searching for clues later, and I have something!</p>
<p>In (Dyalog) APL you can create the uppercase alphabet with just</p>
<pre><code> ⎕A</code></pre>
<pre class="r bg-success"><code>ABCDEFGHIJKLMNOPQRSTUVWXYZ</code></pre>
<p>similar to <code>LETTERS</code>. Lowercase letters can be generated with</p>
<pre><code> 819⌶⎕A</code></pre>
<pre class="r bg-success"><code>abcdefghijklmnopqrstuvwxyz</code></pre>
<p>or (possibly implementation-specific)</p>
<pre><code> ⎕c⎕a</code></pre>
<pre class="r bg-success"><code>abcdefghijklmnopqrstuvwxyz</code></pre>
<p>Selecting random elements from this involves <a href="https://aplwiki.com/wiki/Roll"><code>roll</code></a> with
the syntax</p>
<pre><code> ?5 10⍴26</code></pre>
<pre class="r bg-success"><code>18 16 7 8 22 25 15 17 24 19
18 23 24 9 25 17 4 2 25 24
10 13 6 11 10 17 21 9 15 20
25 8 3 12 4 2 21 3 1 18
2 5 17 19 25 3 3 21 9 4</code></pre>
<p>which produces random values between 1 and the right argument (in this case, a
5x10 reshape of the value <code>26</code> repeated over and over). That’s exactly what we need
as indices to select letters. Putting these together</p>
<pre><code>⎕c⎕a[?5 10⍴26]</code></pre>
<pre class="r bg-success"><code>axpyohnotq
hsrottizwk
dgecrgxbcu
qvvxszptpq
wmaktfuvwf</code></pre>
<p>Even better - if we store the <code>letters</code> like R does, and define a functional
version which takes a left argument (<code>⍺</code>; the shape of the
array), a right argument (<code>⍵</code>; the letters to sample from), and automatically
calculates the length as <code>≢⍵</code>, then the entire solution is</p>
<pre><code>letters←⎕c⎕a
randstrings←{⍵[?⍺⍴≢⍵]}
5 10 randstrings letters</code></pre>
<pre class="r bg-success"><code>npentutsdo
jttcnqeuqm
imgrtupyfx
eliiqnishu
jonkovlmcn</code></pre>
<p>Okay, <em>that’s</em> concise! And, provided you know what <code>?</code>, <code>⍴</code>, and <code>≢</code> do, it’s
fairly readable (in my opinion, at least).</p>
<p>Can you make a better/shorter/more interesting solution to the random strings
problem? Or can improve the Uiua solution? I can be found on
<a href="https://fosstodon.org/@jonocarroll">Mastodon</a> or use the comments below.</p>
<br />
<details>
<summary>
<tt>devtools::session_info()</tt>
</summary>
<pre class="bg-success"><code>## ─ Session info ───────────────────────────────────────────────────────────────
## setting value
## version R version 4.1.2 (2021-11-01)
## os Pop!_OS 22.04 LTS
## system x86_64, linux-gnu
## ui X11
## language (EN)
## collate en_AU.UTF-8
## ctype en_AU.UTF-8
## tz Australia/Adelaide
## date 2023-10-09
## pandoc 3.1.8 @ /usr/lib/rstudio/resources/app/bin/quarto/bin/tools/x86_64/ (via rmarkdown)
##
## ─ Packages ───────────────────────────────────────────────────────────────────
## package * version date (UTC) lib source
## blogdown 1.17 2023-05-16 [1] CRAN (R 4.1.2)
## bookdown 0.29 2022-09-12 [1] CRAN (R 4.1.2)
## bslib 0.5.1 2023-08-11 [3] CRAN (R 4.3.1)
## cachem 1.0.8 2023-05-01 [3] CRAN (R 4.3.0)
## callr 3.7.3 2022-11-02 [3] CRAN (R 4.2.2)
## cli 3.6.1 2023-03-23 [3] CRAN (R 4.2.3)
## crayon 1.5.2 2022-09-29 [3] CRAN (R 4.2.1)
## devtools 2.4.5 2022-10-11 [1] CRAN (R 4.1.2)
## digest 0.6.33 2023-07-07 [3] CRAN (R 4.3.1)
## ellipsis 0.3.2 2021-04-29 [3] CRAN (R 4.1.1)
## evaluate 0.21 2023-05-05 [3] CRAN (R 4.3.0)
## fastmap 1.1.1 2023-02-24 [3] CRAN (R 4.2.2)
## fs 1.6.3 2023-07-20 [3] CRAN (R 4.3.1)
## glue 1.6.2 2022-02-24 [3] CRAN (R 4.2.0)
## htmltools 0.5.6 2023-08-10 [3] CRAN (R 4.3.1)
## htmlwidgets 1.5.4 2021-09-08 [1] CRAN (R 4.1.2)
## httpuv 1.6.6 2022-09-08 [1] CRAN (R 4.1.2)
## jquerylib 0.1.4 2021-04-26 [3] CRAN (R 4.1.2)
## jsonlite 1.8.7 2023-06-29 [3] CRAN (R 4.3.1)
## knitr 1.43 2023-05-25 [3] CRAN (R 4.3.0)
## later 1.3.0 2021-08-18 [1] CRAN (R 4.1.2)
## lifecycle 1.0.3 2022-10-07 [3] CRAN (R 4.2.1)
## magrittr 2.0.3 2022-03-30 [3] CRAN (R 4.2.0)
## memoise 2.0.1 2021-11-26 [3] CRAN (R 4.2.0)
## mime 0.12 2021-09-28 [3] CRAN (R 4.2.0)
## miniUI 0.1.1.1 2018-05-18 [1] CRAN (R 4.1.2)
## pkgbuild 1.4.0 2022-11-27 [1] CRAN (R 4.1.2)
## pkgload 1.3.0 2022-06-27 [1] CRAN (R 4.1.2)
## prettyunits 1.1.1 2020-01-24 [3] CRAN (R 4.0.1)
## processx 3.8.2 2023-06-30 [3] CRAN (R 4.3.1)
## profvis 0.3.7 2020-11-02 [1] CRAN (R 4.1.2)
## promises 1.2.0.1 2021-02-11 [1] CRAN (R 4.1.2)
## ps 1.7.5 2023-04-18 [3] CRAN (R 4.3.0)
## purrr 1.0.1 2023-01-10 [1] CRAN (R 4.1.2)
## R6 2.5.1 2021-08-19 [3] CRAN (R 4.2.0)
## Rcpp 1.0.9 2022-07-08 [1] CRAN (R 4.1.2)
## remotes 2.4.2 2021-11-30 [1] CRAN (R 4.1.2)
## rlang 1.1.1 2023-04-28 [1] CRAN (R 4.1.2)
## rmarkdown 2.24 2023-08-14 [3] CRAN (R 4.3.1)
## rstudioapi 0.15.0 2023-07-07 [3] CRAN (R 4.3.1)
## sass 0.4.7 2023-07-15 [3] CRAN (R 4.3.1)
## sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.1.2)
## shiny 1.7.2 2022-07-19 [1] CRAN (R 4.1.2)
## stringi 1.7.12 2023-01-11 [3] CRAN (R 4.2.2)
## stringr 1.5.0 2022-12-02 [1] CRAN (R 4.1.2)
## urlchecker 1.0.1 2021-11-30 [1] CRAN (R 4.1.2)
## usethis 2.1.6 2022-05-25 [1] CRAN (R 4.1.2)
## vctrs 0.6.3 2023-06-14 [1] CRAN (R 4.1.2)
## xfun 0.40 2023-08-09 [3] CRAN (R 4.3.1)
## xtable 1.8-4 2019-04-21 [1] CRAN (R 4.1.2)
## yaml 2.3.7 2023-01-23 [3] CRAN (R 4.2.2)
##
## [1] /home/jono/R/x86_64-pc-linux-gnu-library/4.1
## [2] /usr/local/lib/R/site-library
## [3] /usr/lib/R/site-library
## [4] /usr/lib/R/library
##
## ──────────────────────────────────────────────────────────────────────────────</code></pre>
</details>
<p><br /></p>
Now You're Thinking with Arrays
https://jcarroll.com.au/2023/08/29/now-you-re-thinking-with-arrays/
Tue, 29 Aug 2023 00:00:00 +0000website@jcarroll.com.au (Jonathan Carroll)https://jcarroll.com.au/2023/08/29/now-you-re-thinking-with-arrays/<p>I keep hearing the assertion that “writing APL/Haskell/etc… makes you think
differently” and I kept wondering why I agreed with the statement but at the same time
didn’t think too much of it. I believe I’ve figured out that it’s <em>because</em> I happened to have
been using Array-aware languages this whole time! It turns out R is an even better
language for beginners than I thought.</p>
<p>Let’s start with some basics. A “scalar” value is just a number by itself. That might
have some units that may or may not be represented well in what you’re doing, but it’s
a single value on its own, like <code>42</code>. A “vector” in R is just a collection of these “scalar”
values and is constructed with the <code>c()</code> operator</p>
<pre class="r"><code>c(3, 4, 5, 6)</code></pre>
<pre class="bg-success"><code>## [1] 3 4 5 6</code></pre>
<p>Going right back to basics, the <code>[1]</code> output at the start of the line indicates the index
of the element directly to its right, in this case the first element. If we had more elements,
then the newline starts with the index of the first element on that line. Here I’ve set the line width smaller than usual so that it wraps sooner</p>
<pre class="r"><code>1:42</code></pre>
<pre class="bg-success"><code>## [1] 1 2 3 4 5 6 7 8 9 10 11 12
## [13] 13 14 15 16 17 18 19 20 21 22 23 24
## [25] 25 26 27 28 29 30 31 32 33 34 35 36
## [37] 37 38 39 40 41 42</code></pre>
<p>The quirk of how R works with vectors is the there aren’t actually <em>any</em> scalar values - if
you try to create a vector with only a single element, it’s still a vector</p>
<pre class="r"><code>x <- c(42)
x</code></pre>
<pre class="bg-success"><code>## [1] 42</code></pre>
<pre class="r"><code>is.vector(x)</code></pre>
<pre class="bg-success"><code>## [1] TRUE</code></pre>
<p>(note the <code>[1]</code> indicating the first index of the vector <code>x</code> <em>and</em> the vector <code>TRUE</code>). Even if you don’t <em>try</em> to make it a vector, it still is one</p>
<pre class="r"><code>x <- 42
x</code></pre>
<pre class="bg-success"><code>## [1] 42</code></pre>
<pre class="r"><code>is.vector(x)</code></pre>
<pre class="bg-success"><code>## [1] TRUE</code></pre>
<p>Mike Mahoney has
<a href="https://www.mm218.dev/posts/2023-08-07-vector/">a great post</a> detailing the term
“vector” and how it relates to an R vector as well as the more mathematical definition
which involves constructing an “arrow” in some space so that you describe both “magnitude”
and “direction” at the same time.</p>
<div class="float">
<img src="images/vector.jpg" alt="“Direction and magnitude”" />
<div class="figcaption">“Direction and magnitude”</div>
</div>
<p>So, we <em>always</em> have a vector if we have a 1-dimensional collection of data. But wait,
you say, there’s also <code>list</code>!</p>
<pre class="r"><code>x <- list(a = 42)
x</code></pre>
<pre class="bg-success"><code>## $a
## [1] 42</code></pre>
<pre class="r"><code>is.vector(x)</code></pre>
<pre class="bg-success"><code>## [1] TRUE</code></pre>
<p>A nice try, but lists are also vectors, it’s just that they’re “recursive”</p>
<pre class="r"><code>is.recursive(x)</code></pre>
<pre class="bg-success"><code>## [1] TRUE</code></pre>
<pre class="r"><code>is.recursive(c(42))</code></pre>
<pre class="bg-success"><code>## [1] FALSE</code></pre>
<p>Fine, what about a matrix, then?</p>
<pre class="r"><code>x <- matrix(1:9, nrow = 3, ncol = 3)
x</code></pre>
<pre class="bg-success"><code>## [,1] [,2] [,3]
## [1,] 1 4 7
## [2,] 2 5 8
## [3,] 3 6 9</code></pre>
<pre class="r"><code>is.vector(x)</code></pre>
<pre class="bg-success"><code>## [1] FALSE</code></pre>
<p>No, but that still makes sense - a matrix isn’t a vector. It <em>is</em> however, an “array” - the
naming convention in R is a bit messy because, while “matrix” and “array” are often the
same thing, as the dimensions increase, more things expect an “array” class, so R tags
a “matrix” with both</p>
<pre class="r"><code>class(matrix())</code></pre>
<pre class="bg-success"><code>## [1] "matrix" "array"</code></pre>
<p>This recently surprised Josiah Parry leading to <a href="https://josiahparry.com/posts/2023-06-11-matrix-bug">this post</a> explaining some of the internal
inconsistencies of this class of object.</p>
<p>Now that we have vectors figured out, I can get to the point of this post - that
thinking about data with a “vector” or even “array” mindset works differently.</p>
<p>I started learning some APL because I loved some videos by <a href="https://www.youtube.com/@code_report">code_report</a>. The person
behind those is Conor Hoekstra. I didn’t realise that I’d actually heard a
<a href="https://corecursive.com/065-competitive-coding-with-conor-hoekstra/">CoRecursive</a>
podcast episode interviewing him, so now I need to go back and re-listen to that one. Conor
also hosts <a href="https://www.arraycast.com/">The Array Cast</a> podcast that I heard him
mention in yet another <a href="https://adspthepodcast.com/">of his podcasts</a> (how do
people have the time to make all of these!?!). I was listening to the <a href="https://www.arraycast.com/episodes/episode60-rob-pike">latest of these;
an interview with Rob Pike</a>, one
of the co-creators of Go and UTF-8 - it’s a really interesting interview full of history,
insights, and a lot of serious name dropping.</p>
<p>Anyway, Rob is describing what it is he really likes about APL and says</p>
<blockquote>
<p>“I saw a talk some time ago, I wish I could remember where it was, where somebody said, this is why programming languages are so difficult for people. Let’s say that I have a list of numbers and I want to add seven to every number on that list. And he went through about a dozen languages showing how you create a list of numbers and then add seven to it. Right? And it went on and on and on. And he said,”Wouldn’t it be nice if you could just type 7+ and then write the list of numbers?” And he said, “Well, you know what? There’s one language that actually does that, and that’s APL.” And I think there’s something really profound in that, that there’s no ceremony in APL. If you want to add two numbers together in any language, you can add two numbers together. But if you want to add a matrix and a matrix, or a matrix and a vector, or a vector and a scaler or whatever, there’s no extra ceremony involved. You just write it down.</p>
</blockquote>
<p>(<a href="https://www.arraycast.com/episode-60-transcript#:~:text=I%20saw%20a%20talk%20some%20time%20ago">link to shownotes</a>)</p>
<p>The talk he mentions is linked in the shownotes as <a href="https://youtu.be/8Ab3ArE8W3s&t=434">“Stop Writing Dead Programs” by Jack Rusher (Strange Loop 2022)</a> (linked to the relevant timestamp, and
which I’m pretty sure I’ve watched before - it’s a great talk!) where
Jack shows how to add <code>1</code> to a vector of values in a handful of languages. He demonstrates that in
some languages there’s lots you need to write that has <strong>nothing</strong> to do with the problem itself;
allocating memory, looping to some length, etc… then leads to demonstrating that the way to
do this in APL is</p>
<pre class="r"><code>1 + 1 2 3 4</code></pre>
<p>with <strong>none</strong> of the overhead - just the declaration of what operation should occur.</p>
<p>The excitement with which Rob explains this in the podcast spoke to how important this
idea is; that you can work with more than just scalar values in the mathematical sense
without having to explain to the language <em>what you mean</em> and write a loop around a vector.</p>
<p>Two questions were buzzing at the back of my mind, though:</p>
<ol style="list-style-type: decimal">
<li><p>Why isn’t this such a revelation to me?</p></li>
<li><p>Is this not a common feature?</p></li>
</ol>
<p>I <em>know</em> R does work this way because I’m very familiar with it, and perhaps that <em>is</em>
the answer to the first question - I know R better than any other language I know, and
perhaps I’ve just become accustomed to being able to do things like “add two vectors”.</p>
<pre class="r"><code>a <- c(1, 2, 3, 4, 5) # or 1:5
a + 7</code></pre>
<pre class="bg-success"><code>## [1] 8 9 10 11 12</code></pre>
<div class="float">
<img src="images/portals.jpg" alt="Now you’re thinking with portals vectors!" />
<div class="figcaption">Now you’re thinking with <s>portals</s> vectors!</div>
</div>
<p>The ideas of “add two vectors” and “add a number to a vector” are one in the same, as discussed above. The ability to do so is called <a href="https://en.wikipedia.org/wiki/Polymorphism_(computer_science)#Rank_polymorphism">“rank polymorphism”</a> and
R has a weak version of it - not everything works for every dimension, but single values, vectors, and matrices do generalise for many functions. I can add a value to every element of a matrix, too</p>
<pre class="r"><code>m <- matrix(1:12, nrow = 3, ncol = 4)
m</code></pre>
<pre class="bg-success"><code>## [,1] [,2] [,3] [,4]
## [1,] 1 4 7 10
## [2,] 2 5 8 11
## [3,] 3 6 9 12</code></pre>
<pre class="r"><code>m + 7</code></pre>
<pre class="bg-success"><code>## [,1] [,2] [,3] [,4]
## [1,] 8 11 14 17
## [2,] 9 12 15 18
## [3,] 10 13 16 19</code></pre>
<p>and adding a vector to a matrix repeats the operation over rows</p>
<pre class="r"><code>m <- matrix(1, nrow = 3, ncol = 4)
m</code></pre>
<pre class="bg-success"><code>## [,1] [,2] [,3] [,4]
## [1,] 1 1 1 1
## [2,] 1 1 1 1
## [3,] 1 1 1 1</code></pre>
<pre class="r"><code>v <- c(11, 22, 33)
m + v</code></pre>
<pre class="bg-success"><code>## [,1] [,2] [,3] [,4]
## [1,] 12 12 12 12
## [2,] 23 23 23 23
## [3,] 34 34 34 34</code></pre>
<p>Sidenote: the distinction between “repeat over rows” and “repeat over columns” is also
discussed in the Array Cast episode - if you want to know more, there’s <a href="https://aplwiki.com/wiki/Leading_axis_theory">“leading axis theory”</a>. R uses column-major order which
is why the matrix <code>m</code> filled the sequential values down the first column, and why you need
to specify <code>byrow = TRUE</code> if you want to fill the other way. It’s also why <code>m + v</code> repeats
over rows, although if you are expecting it to repeat over columns and try to use a <code>v</code> with 4
elements it will (silently) work, recycling the vector <code>v</code>, and giving you something you
didn’t expect</p>
<pre class="r"><code>v <- c(11, 22, 33, 44)
m + v</code></pre>
<pre class="bg-success"><code>## [,1] [,2] [,3] [,4]
## [1,] 12 45 34 23
## [2,] 23 12 45 34
## [3,] 34 23 12 45</code></pre>
<p>{reticulate} has a really <a href="https://cran.r-project.org/web/packages/reticulate/vignettes/arrays.html">nice explainer</a>
of the differences between R (column-major) and python (row-major), and importantly,
the interop between these two.</p>
<p>So, is working with arrays actually so uncommon? I first thought of Julia,
and since it’s much newer than R and took a lot of inspiration from a variety
of languages, perhaps it works</p>
<pre class="julia"><code>a = [1, 2, 3, 4, 5]
a + 7</code></pre>
<pre class="r bg-danger"><code>ERROR: MethodError: no method matching +(::Vector{Int64}, ::Int64)
For element-wise addition, use broadcasting with dot syntax: array .+ scalar</code></pre>
<p>Not quite, but the error message is extremely helpful. Julia wants to perform
element-wise addition using the broadcasting operator <code>.</code> so it needs to be</p>
<pre class="julia"><code>a .+ 7</code></pre>
<pre class="r bg-success"><code>5-element Vector{Int64}:
8
9
10
11
12</code></pre>
<p>Still, that’s a “know the language” thing that’s outside of “add a number to a vector”,
so no credit.</p>
<p>Well, what about python?</p>
<pre class="python"><code>a = [1, 2, 3, 4, 5]
a + 7</code></pre>
<pre class="r bg-danger"><code>Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "int") to list</code></pre>
<p>The canonical way, I believe, is to use a list comprehension</p>
<pre class="python"><code>a = [1, 2, 3, 4, 5]
[i + 7 for i in a]</code></pre>
<pre class="r bg-success"><code>[8, 9, 10, 11, 12]</code></pre>
<p>and we’re once more using a language feature that’s outside of “add a number to a
vector” so again, no credit. For the pedants: there is library support for this
if you use numpy</p>
<pre class="python"><code>import numpy as np
a = [1, 2, 3, 4, 5]
np.array(a) + 7</code></pre>
<pre class="r bg-success"><code>array([ 8, 9, 10, 11, 12])</code></pre>
<p>but I wouldn’t call that a success.</p>
<p>I asked ChatGPT what other languages could do this and it suggested MATLAB. Now,
that’s a proprietary language I don’t have access to, but octave is an open-source
alternative that is more or less the same, so I tried that</p>
<pre class="r"><code>a = [1, 2, 3, 4, 5];
a</code></pre>
<pre class="r bg-success"><code>a =
1 2 3 4 5</code></pre>
<pre class="r"><code>a + 7</code></pre>
<pre class="r bg-success"><code>ans =
8 9 10 11 12</code></pre>
<p>Okay, yeah - a win for MATLAB. I remember using MATLAB back at Uni in
an early (second year?) Maths (differential equations?) course and it was
probably the very first time I was actually
introduced to a programming language. IIRC, “everything is a matrix” (which works
out okay for engineering and maths use-cases) so this a) probably isn’t surprising that
it works, and b) makes sense that it gets lumped in with the “array languages”.</p>
<p>Thinking back to the other programming languages I’ve learned sufficiently, I wondered how Fortran
dealt with this - I used Fortran (90) for all of my PhD and postdoc calculations.
I loved that Fortran had vectors (and n-dimensional arrays) without
having to do any manual memory allocation, and for that reason alone it was well-suited to
theoretical physics modeling. I’ve been re-learning some Fortran via Exercism, so
I gave that a go</p>
<pre class="r"><code>$ cat array.f90</code></pre>
<pre class="r bg-success"><code>program add_to_array
implicit none
integer, dimension(5) :: a
a = (/1, 2, 3, 4, 5/)
print *, a + 7
end program add_to_array</code></pre>
<p>Compiling and running this…</p>
<pre class="r"><code>$ gfortran -o array array.f90
$ ./array</code></pre>
<pre class="r bg-success"><code> 8 9 10 11 12</code></pre>
<p>A win! Okay, a little ceremony to declare the vector itself, but that’s strict
typing for you.</p>
<p>With these results at hand, I think back to the question</p>
<blockquote>
<p>Why isn’t this such a revelation to me?</p>
</blockquote>
<p>I learned MATLAB, Fortran, then R, over the course of about a decade, and barely
touched other languages with any seriousness while doing so… I’ve been using
array languages more or less exclusively all this time.</p>
<div class="float">
<img src="images/bane.jpg" alt="“You merely learned to use arrays, I was born in them, molded by them”" />
<div class="figcaption">“You merely learned to use arrays, I was born in them, molded by them”</div>
</div>
<p>Better still, they’re all <em>column-major</em> array languages.</p>
<p>I think <em>this</em> is why APL seems to beautiful to me - it does what I <em>know</em> I want and
it does it with the least amount of ceremony.</p>
<p>I wrote a bit about this in <a href="https://jcarroll.com.au/2023/07/07/array-languages-r-vs-apl/">a previous post</a> - that a language
can hide some complexity for you, like the fact that it <em>does</em> need to internally do a loop
over some elements in order to add two vectors, but when the language itself provides
an interface where you don’t have to worry about that, things get beautiful.</p>
<p>At PyConAU this year there was a keynote <a href="https://youtu.be/zUyR-Qbr4Yg">“The Complexity of Simplicity”</a> which reminded me a lot of another post
<a href="https://ferd.ca/complexity-has-to-live-somewhere.html">“Complexity Has to Live Somewhere”</a>.
I think APL really nailed removing a lot of the syntax complexity of a language, leaving
mainly just the operations you wish to perform. Haskell does similar but adds back in
(albeit, useful) language features that involve syntax.</p>
<p>Of the languages I did learn first, I would have to say that R wins over MATLAB and Fortran
in terms of suitability as a first programming language, but now that I recognise that the
“array” way of thinking comes along with that, I really do think it has a big advantage
over, say, python in terms of shaping that mindset. Sure, if you start out with numpy you
may gain that same advantage, but either way I would like to think there’s a lot to be
gained from starting with an “array-aware” language.</p>
<p>Did I overlook another language that can work so nicely with arrays? Have you reflected on
how you think in terms of arrays and programming in general? Let me know in the comments or
on <a href="https://fosstodon.org/@jonocarroll">Mastodon</a>.</p>
<br />
<details>
<summary>
<tt>devtools::session_info()</tt>
</summary>
<pre class="bg-success"><code>## ─ Session info ───────────────────────────────────────────────────────────────
## setting value
## version R version 4.1.2 (2021-11-01)
## os Pop!_OS 22.04 LTS
## system x86_64, linux-gnu
## ui X11
## language (EN)
## collate en_AU.UTF-8
## ctype en_AU.UTF-8
## tz Australia/Adelaide
## date 2023-08-29
## pandoc 3.1.1 @ /usr/lib/rstudio/resources/app/bin/quarto/bin/tools/ (via rmarkdown)
##
## ─ Packages ───────────────────────────────────────────────────────────────────
## package * version date (UTC) lib source
## blogdown 1.17 2023-05-16 [1] CRAN (R 4.1.2)
## bookdown 0.29 2022-09-12 [1] CRAN (R 4.1.2)
## bslib 0.5.0 2023-06-09 [3] CRAN (R 4.3.1)
## cachem 1.0.8 2023-05-01 [3] CRAN (R 4.3.0)
## callr 3.7.3 2022-11-02 [3] CRAN (R 4.2.2)
## cli 3.6.1 2023-03-23 [3] CRAN (R 4.2.3)
## crayon 1.5.2 2022-09-29 [3] CRAN (R 4.2.1)
## devtools 2.4.5 2022-10-11 [1] CRAN (R 4.1.2)
## digest 0.6.33 2023-07-07 [3] CRAN (R 4.3.1)
## ellipsis 0.3.2 2021-04-29 [3] CRAN (R 4.1.1)
## evaluate 0.21 2023-05-05 [3] CRAN (R 4.3.0)
## fastmap 1.1.1 2023-02-24 [3] CRAN (R 4.2.2)
## fs 1.6.3 2023-07-20 [3] CRAN (R 4.3.1)
## glue 1.6.2 2022-02-24 [3] CRAN (R 4.2.0)
## htmltools 0.5.6 2023-08-10 [3] CRAN (R 4.3.1)
## htmlwidgets 1.5.4 2021-09-08 [1] CRAN (R 4.1.2)
## httpuv 1.6.6 2022-09-08 [1] CRAN (R 4.1.2)
## jquerylib 0.1.4 2021-04-26 [3] CRAN (R 4.1.2)
## jsonlite 1.8.7 2023-06-29 [3] CRAN (R 4.3.1)
## knitr 1.43 2023-05-25 [3] CRAN (R 4.3.0)
## later 1.3.0 2021-08-18 [1] CRAN (R 4.1.2)
## lifecycle 1.0.3 2022-10-07 [3] CRAN (R 4.2.1)
## magrittr 2.0.3 2022-03-30 [3] CRAN (R 4.2.0)
## memoise 2.0.1 2021-11-26 [3] CRAN (R 4.2.0)
## mime 0.12 2021-09-28 [3] CRAN (R 4.2.0)
## miniUI 0.1.1.1 2018-05-18 [1] CRAN (R 4.1.2)
## pkgbuild 1.4.0 2022-11-27 [1] CRAN (R 4.1.2)
## pkgload 1.3.0 2022-06-27 [1] CRAN (R 4.1.2)
## prettyunits 1.1.1 2020-01-24 [3] CRAN (R 4.0.1)
## processx 3.8.2 2023-06-30 [3] CRAN (R 4.3.1)
## profvis 0.3.7 2020-11-02 [1] CRAN (R 4.1.2)
## promises 1.2.0.1 2021-02-11 [1] CRAN (R 4.1.2)
## ps 1.7.5 2023-04-18 [3] CRAN (R 4.3.0)
## purrr 1.0.1 2023-01-10 [1] CRAN (R 4.1.2)
## R6 2.5.1 2021-08-19 [3] CRAN (R 4.2.0)
## Rcpp 1.0.9 2022-07-08 [1] CRAN (R 4.1.2)
## remotes 2.4.2 2021-11-30 [1] CRAN (R 4.1.2)
## rlang 1.1.1 2023-04-28 [1] CRAN (R 4.1.2)
## rmarkdown 2.23 2023-07-01 [3] CRAN (R 4.3.1)
## rstudioapi 0.15.0 2023-07-07 [3] CRAN (R 4.3.1)
## sass 0.4.7 2023-07-15 [3] CRAN (R 4.3.1)
## sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.1.2)
## shiny 1.7.2 2022-07-19 [1] CRAN (R 4.1.2)
## stringi 1.7.12 2023-01-11 [3] CRAN (R 4.2.2)
## stringr 1.5.0 2022-12-02 [1] CRAN (R 4.1.2)
## urlchecker 1.0.1 2021-11-30 [1] CRAN (R 4.1.2)
## usethis 2.1.6 2022-05-25 [1] CRAN (R 4.1.2)
## vctrs 0.6.3 2023-06-14 [1] CRAN (R 4.1.2)
## xfun 0.40 2023-08-09 [3] CRAN (R 4.3.1)
## xtable 1.8-4 2019-04-21 [1] CRAN (R 4.1.2)
## yaml 2.3.7 2023-01-23 [3] CRAN (R 4.2.2)
##
## [1] /home/jono/R/x86_64-pc-linux-gnu-library/4.1
## [2] /usr/local/lib/R/site-library
## [3] /usr/lib/R/site-library
## [4] /usr/lib/R/library
##
## ──────────────────────────────────────────────────────────────────────────────</code></pre>
</details>
<p><br /></p>
Array Languages: R vs APL
https://jcarroll.com.au/2023/07/07/array-languages-r-vs-apl/
Fri, 07 Jul 2023 00:00:00 +0000website@jcarroll.com.au (Jonathan Carroll)https://jcarroll.com.au/2023/07/07/array-languages-r-vs-apl/<hr />
<p><strong>Update</strong>: this post was discussed late March 2024 on
<a href="https://news.ycombinator.com/item?id=39771878">HackerNews</a> and <a href="https://lobste.rs/s/dhwo8a/array_languages_r_vs_apl">lobste.rs</a>.</p>
<hr />
<p>I’ve been learning at least one new programming language a month through
<a href="https://exercism.org/">Exercism</a> which has been really fun and interesting. I frequently say that “every language you learn teaches you something about all the
others you know” and with nearly a dozen under my belt so far I’m starting to worry about the combinatorics of that statement.</p>
<p>APL isn’t on the list of languages but I’ve seen it in <a href="https://codegolf.stackexchange.com/">codegolf</a> solutions often enough that it
seemed worth a look.</p>
<p>Now, when I say “learning” I mean “good enough to do 5 toy exercises” which is
what you need to do to in order to earn the badge for that month in the <a href="https://exercism.org/challenges/12in23">“#12in23
challenge”</a> (gamification FTW). That’s often
sufficient for me to get a taste for the language and see if it’s something I’d
like to dive deeper into.</p>
<p>It means I’ve been watching a lot of “<language> beginner tutorial” videos recently,
which may have been what prompted YouTube to suggest to me a video from
<a href="https://www.youtube.com/@code_report">code_report</a>; I think <a href="https://www.youtube.com/watch?v=UVUjnzpQKUo">this one</a>
comparing a <a href="https://leetcode.com/">leetcode</a> solution to the problem</p>
<blockquote>
<p>find the GCD (greatest common divisor) of the smallest and largest numbers in an array</p>
</blockquote>
<p>written in 16 (sixteen!!!) languages. Some of those I know a little or a moderate
amount about, but one stood out. The APL solution comprises 5 glyphs (symbols)
representing operations</p>
<pre class="apl"><code> ⌈/∨⌊/</code></pre>
<p>I’ve seen APL solutions pop up in codegolf and they’ve just looked like madness,
which is probably fair. The linked video prompted me to look into some of their
other videos and they do a great job explaining the glyphs in APL and how they
compare to other languages. It turns out this madness is not nearly as hard to
read as it looks. The above glyphs represent “maximum” (⌈), “reduce” (/), “GCD”
(∨), and “minimum” (⌊) and those all correspond well to the problem statement.
The function itself is
<a href="https://en.wikipedia.org/wiki/Tacit_programming">“point-free”</a> whereby the
argument(s) aren’t specified at all; like saying <code>mean</code> rather than <code>mean(x)</code>.
For the truly adventurous: <a href="https://blog.devgenius.io/the-hideous-beauty-of-point-free-programming-e8608e3df09d">‘The Hideous Beauty of Point-Free Programming; An
exercise in combinators using
Haskell’</a></p>
<p>I ended up diving deeper and deeper, and it all started to make more and more sense.</p>
<p>In a recent stream, <a href="https://www.youtube.com/@ThePrimeagen">ThePrimeagen</a> responded
to the comment about some language that “<x> is more readable” with “readability is
just familiarity” and that stuck with me - I’m not entirely sure I 100% agree with it
because I can find several ways to write some code that someone familiar with that
language will either find easy or hard to read, despite familiarity. I think {dplyr} in R
does a fantastic job of abstracting operations with verbs and making data pipelines
easy to comprehend, certainly much more than the base-equivalent code.</p>
<p>So, would APL be “readable” if I was more familiar with it? Let’s find out!</p>
<p>There aren’t <em>that</em> many glyphs in APL - there are far more unique functions in
most big libraries from any mainstream language. Looking at the top of the ‘ride’
editor for Dyalog APL there are 80 glyphs. To make a slightly unfair example, there
are a lot of exported functions (288 of them) in {dplyr}…</p>
<pre class="r"><code>packageVersion("dplyr")</code></pre>
<pre class="bg-success"><code>## [1] '1.0.10'</code></pre>
<pre class="r"><code>ns <- sort(getNamespaceExports("dplyr"))
head(ns, 20)</code></pre>
<pre class="bg-success"><code>## [1] ".data" "%>%" "across" "add_count" "add_count_"
## [6] "add_row" "add_rownames" "add_tally" "add_tally_" "all_equal"
## [11] "all_of" "all_vars" "anti_join" "any_of" "any_vars"
## [16] "arrange" "arrange_" "arrange_all" "arrange_at" "arrange_if"</code></pre>
<pre class="r"><code>tail(ns, 20)</code></pre>
<pre class="bg-success"><code>## [1] "tbl_vars" "tibble" "top_frac"
## [4] "top_n" "transmute" "transmute_"
## [7] "transmute_all" "transmute_at" "transmute_if"
## [10] "tribble" "type_sum" "ungroup"
## [13] "union" "union_all" "validate_grouped_df"
## [16] "validate_rowwise_df" "vars" "with_groups"
## [19] "with_order" "wrap_dbplyr_obj"</code></pre>
<p>Taking the functions listed as <code>S3method</code> or <code>export</code> in the
<a href="https://github.com/tidyverse/dplyr/blob/main/NAMESPACE"><code>NAMESPACE</code></a>
file is 470+. Sure, these aren’t <em>all</em> user-facing, but still. Lots.</p>
<p>So, 80 isn’t a “huge” number, if that’s the <strong>entire language</strong>.</p>
<p>I watched some more videos about what the glyphs mean and how they work. I
started to become slightly familiar with what they mean. Learning is done with
the hands, not the eyes, though - as <a href="https://giansegato.com/essays/edutainment-is-not-learning">this (not new) blog post</a> goes into great
detail on, so
I felt that I needed to actually write something. I installed Dyalog APL and the
ride editor (given that it uses glyphs, a non-standard editor seems to make sense;
I’ve otherwise been completing the Exercism solutions in emacs). I also found <a href="https://tryapl.org">tryapl.org</a> as an online editor.</p>
<p>The first step was to just follow along what I’d seen in the videos. I had
most recently watched <a href="https://www.youtube.com/watch?v=8ynsN4nJxzU">this one</a> that
does include a comparison to R (and Julia) so I tried to recreate what I’d seen
built up. I was shocked that I actually could!</p>
<div class="float">
<img src="images/49288d6d46f5baac.png" alt="Recreating construction of an X-matrix in APL using tryapl.org" />
<div class="figcaption">Recreating construction of an X-matrix in APL using tryapl.org</div>
</div>
<p>From reshaping into a matrix, to building up the sequence, to inserting the
combinator - it all came together easily enough.</p>
<p>On “combinators” - if you aren’t familiar with Lambda Calculus and have a spare hour,
<a href="https://youtu.be/3VQ382QG-y4">this</a> is a wonderful talk explaining the basics
and demonstrating them using JavaScript.</p>
<p>More videos, more learning. I found <a href="https://youtu.be/MKb4WD6mioE">this one</a>
which is another leetcode problem which was roughly</p>
<blockquote>
<p>find the maximum value of the sum of rows of a matrix</p>
</blockquote>
<p>That sounded like something R would easily handle, but this particular
video didn’t feature R. It <em>did</em> feature C++, the solution for which
requires two <code>for</code> loops and looked (to me) horrific - I’m used to just
passing a matrix to an R function and not having to worry about loops.</p>
<p>I’ve had many discussions on this topic because for whatever reason, <code>for</code>
loops have a particular reputation in R despite them not (necessarily) being
any worse than any other solution. The short response is that if you’re using one
when you could be using vectorisation, you’re probably stating your problem poorly
and can do better (in terms of readability, performance, or both). <a href="https://youtu.be/TdbweYvwnss">This video</a>
covers the points really nicely.</p>
<p>Jenny Bryan <a href="https://speakerdeck.com/jennybc/row-oriented-workflows-in-r-with-the-tidyverse?slide=16">made the point</a> that</p>
<blockquote>
<p>Of course someone has to write loops… It doesn’t have to be you</p>
</blockquote>
<p>alluding to the fact that vectorisation (either with the <code>*apply</code> family or <code>purrr</code>)
still has a C loop buried within (I covered some of this myself in <a href="https://jcarroll.com.au/2022/04/22/where-for-loop-art-thou/">another post</a>).</p>
<p><a href="https://fosstodon.org/@milesmcbain/110658467908744395">Miles McBain makes a point of never using them</a> (directly).</p>
<p>Okay, so, returning to the leetcode problem. The APL solution in the video is
reshaping (<code>⍴</code>) a vector to a matrix then reducing (<code>/</code>) addition (<code>+</code>) across rows (last-axis; c.f. first axis would be <code>+⌿</code>) and reducing (<code>/</code>) that with maximum (<code>⌈</code>)
making the entire solution</p>
<pre class="apl"><code> x ← 3 3⍴1 2 3 5 5 5 3 1 4
⌈/+/x
15</code></pre>
<p>which is an elegant, compact solution. APL agrees to ignore the <code>[1]</code> at the
start of R’s default output if R agrees to ignore the odd indenting of APL commands.</p>
<p>As a sidenote: I <strong>love</strong> that I finally get to use the OG assignment arrow <code>←</code>
that inspired the usage in R (as <code><-</code>). This isn’t some ligature font, it’s the
actual arrow glyph with Unicode code point U+2190. The APL keyboard has this on
a key and that was common around the time that it made it into R (or S).</p>
<p>The video explains that this solution is
particularly nice because it’s explicit that two “reduce” operations
are occurring. The <code>+</code> operator in APL can be either unary (takes 1 argument) or
binary (takes 2 arguments) but it can’t loop over an entire vector. To achieve that,
it’s combined with <code>/</code> which performs “reduce”, essentially applying <code>+</code> across
the input.</p>
<p>It’s a fairly straightforward answer with R, too:</p>
<pre class="r"><code>a <- matrix(c(1, 2, 3,
5, 5, 5,
3, 1, 4),
3, 3, byrow = TRUE)
a</code></pre>
<pre class="bg-success"><code>## [,1] [,2] [,3]
## [1,] 1 2 3
## [2,] 5 5 5
## [3,] 3 1 4</code></pre>
<pre class="r"><code>max(rowSums(a))</code></pre>
<pre class="bg-success"><code>## [1] 15</code></pre>
<p>and done. Nice. No <code>for</code> loops. Or are there? Of course there are, somewhere, but
can we write this “like” the APL solution and be more explicit with the “reduce”
steps over binary operators? R has a <code>Reduce()</code> function for exactly this case.</p>
<p>A simplified <code>rowSums()</code> function could just be applying the <code>sum</code> operation to
the rows of the matrix</p>
<pre class="r"><code>s <- function(x) apply(x, 1, sum)</code></pre>
<p>but <code>sum(x)</code> is itself vectorised - it’s an application of the binary <code>+</code> operation
across a vector, so really we could have</p>
<pre class="r"><code>s <- function(x) apply(x, 1, \(y) Reduce(`+`, y))
s(a)</code></pre>
<pre class="bg-success"><code>## [1] 6 15 8</code></pre>
<p>This isn’t so bad compared to APL which “naturally” performs the reduction
over that dimension. Compare (<code>⍝</code> signifies a comment):</p>
<pre class="apl"><code> x
1 2 3
5 5 5
3 1 4
⍝ "rowSums"
+/x
6 15 8
⍝ "colSums"
+⌿x
9 8 12</code></pre>
<p>There’s nothing here that says <code>x</code> <em>needs</em> to have more than 1 dimension, though -
it’s the same operator(s) on a vector, just that they do the same thing</p>
<pre class="apl"><code> +/(1 2 3)
6
+⌿(1 2 3)
6</code></pre>
<p><code>max</code> is also vectorised, so a simple, ostensibly binary version of that could be</p>
<pre class="r"><code>m <- function(x, y) ifelse(x > y, x, y)
m(1, 2)</code></pre>
<pre class="bg-success"><code>## [1] 2</code></pre>
<pre class="r"><code>m(4, 2)</code></pre>
<pre class="bg-success"><code>## [1] 4</code></pre>
<p>Together, an R solution using these could be</p>
<pre class="r"><code>Reduce(m, s(a))</code></pre>
<pre class="bg-success"><code>## [1] 15</code></pre>
<p>which, if we shortened <code>Reduce</code> to a single character</p>
<pre class="r"><code>R <- Reduce</code></pre>
<p>would be</p>
<pre class="r"><code>R(m, s(a))</code></pre>
<pre class="bg-success"><code>## [1] 15</code></pre>
<p>That’s not a lot more characters than APL. I’ve abstracted at least one of the functions, though - APL uses the operators directly, in which case we’d have</p>
<pre class="r"><code>maxWealth <- \(x) R(m, apply(x, 1, \(y) R(`+`, y)))
maxWealth(a)</code></pre>
<pre class="bg-success"><code>## [1] 15</code></pre>
<p>That’s <em>only</em> using <code>Reduce</code>, binary <code>+</code>, a simplified <code>max</code> (which we could
imagine was a built-in we could shorten to <code>m</code>), and the <code>apply</code> over rows.</p>
<p>Comparing these directly (with some artistic license):</p>
<pre><code> m R + R
⌈ / + /</code></pre>
<p>The point of this whole exercise wasn’t to rebuild the APL solution in R - it
was to think more deeply about what abstractions R offers and how they compare
to a language that uses (only) the atomic constructs directly.</p>
<p>I <em>love</em> that in R I can pass either individual values or a vector to <code>sum</code>
and it “just deals with it”</p>
<pre class="r"><code>sum(4, 5, 6) # sum some "scalars"</code></pre>
<pre class="bg-success"><code>## [1] 15</code></pre>
<pre class="r"><code>vals <- c(4, 5, 6)
sum(vals) # sum a vector</code></pre>
<pre class="bg-success"><code>## [1] 15</code></pre>
<p>This ability to hide/abstract the looping over dimensions and to work directly with
objects with more than one dimension is what <a href="https://en.wikipedia.org/wiki/Array_programming#R">qualifies R as an “array language”</a>.
This is also (mimicking, perhaps) <a href="https://www.jernesto.com/articles/rapl.html">“rank polymorphism”</a> which APL does have. Julia
gets around this with “broadcasting”. But, at least in R, this hides/abstracts some of what is happening, and sometimes/often, that’s a <code>for</code> loop.</p>
<p>Does every programmer need to know the gory details? Absolutely not. Might it be
useful for gaining a better understanding of the language and how to work with it?
I really think it is. It’s why I’m digging further and further into functional
programming in general.</p>
<p>I do believe that the APL solution is more explicit in what it’s doing; that it
doesn’t hide (much, if any) of the implementation details. I’m comfortable
with the abstractions in R and will continue to write R for this reason, but if
I had a need to do some array math in any other language, I now feel like APL
really does have a lot to offer.</p>
<p><strong>Bonus Round</strong></p>
<p>I was thinking about the leetcode problem and thought that a slightly more
complex version would be to return “which row has the maximum?” rather than
the maximum itself.</p>
<p>In R, there is another useful function to achieve this</p>
<pre class="r"><code>which.max(rowSums(a))</code></pre>
<pre class="bg-success"><code>## [1] 2</code></pre>
<p>so, have I learned enough APL to do this myself?</p>
<p>There’s a “Grade Down” operator (<code>⍒</code>) which seems equivalent to R’s
<code>order(decreasing = TRUE)</code> and a “First” operator (<code>⊃</code>) like <code>head(n = 1)</code>
so a solution seems to be to get the indices of the sorted (decreasing)
elements then take the first one</p>
<pre class="apl"><code> ⊃⍒+/x
2</code></pre>
<p>Apparently an alternative would be to find the (first) element of the input (<code>⍵</code>) that
matches the maximum which would be</p>
<pre class="apl"><code> {⍵⍳⌈/⍵}(+/x)
2</code></pre>
<p>which, at least to me, isn’t as elegant.</p>
<p>Lastly, <a href="https://mastodon.social/@kjhealy/110661489858307306">Kieran Healy</a>
relayed to me a small algorithm for finding ‘primes smaller
than some number’ in APL which cleaned up as</p>
<pre class="apl"><code> ((⊢~∘.×⍨)1↓⍳)(50)
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47</code></pre>
<p>This makes use of some combinators (e.g. the C-combinator <code>⍨</code> - possibly the
coolest glyph in the entire system),
but roughly involves filtering values not (<code>~</code>)
members (<code>∈</code>) of values produced by the outer (<code>º</code>) product (<code>.</code>) using
multiplication (<code>×</code>) (i.e. numbers that can be made by multiplying other
numbers) from the sequence (<code>⍳</code>) from 2 to some value (dropping (<code>↓</code>) 1;
<code>3↓⍳8 == 4:8</code>). With the small amount I’ve learned - mostly from watching
someone else use the language - I was able to decipher at least <em>what</em> the
operators were in all of that, even if I probably couldn’t come up with the
solution myself.</p>
<p>I’m happy to call that “readable”.</p>
<p>I looked around for code to generate the primes below some number in R. I couldn’t (easily) find one that worked without an explicit loop. I found
a version in <a href="https://github.com/mmaechler/sfsmisc/blob/81015322032edd9f900e5103ac11c70de49619bd/R/prime-numbers-fn.R#L15-L31">{sfsmisc}</a> which compacts to</p>
<pre class="r"><code>primes. <- function(n) {
## By Bill Venables <= 2001
x <- 1:n
x[1] <- 0
p <- 1
while((p <- p + 1) <= floor(sqrt(n)))
if(x[p] != 0)
x[seq(p^2, n, p)] <- 0
x[x > 0]
}
primes.(50)</code></pre>
<pre class="bg-success"><code>## [1] 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47</code></pre>
<p>Taking inspiration from the APL solution, though - what if we just generate all
products from the set of numbers <code>2:n</code> and exclude those as “not prime” from all
the numbers up to <code>n</code>?</p>
<pre class="r"><code>primes <- function(n) {
s <- 2:n
setdiff(s, c(outer(s, s, `*`)))
}
primes(50)</code></pre>
<pre class="bg-success"><code>## [1] 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47</code></pre>
<p>That… works! It’s slower and uses more memory, for sure, but that wasn’t
our criteria, and isn’t relevant for a once-off evaluation. Even better - I can
“read” exactly what it’s doing.</p>
<p>I’ve learned a lot and I’ll continue to learn more about APL because I really do
think that understanding how these operators come together to build a function
will be enlightening in terms of a functional approach.</p>
<p>I still haven’t made it to trying out BQN (<em>almost</em> constructed by incrementing
each letter of APL, <code>IBM -> HAL</code> style, but perhaps officially
<a href="https://mlochbaum.github.io/BQN/">“Big Questions Notation”</a>, and sometimes
pronounced “bacon”) but it sounds like it has some newer improvements over APL
and will be worth a try.</p>
<p>As always, comments and discussions are welcome here or on <a href="https://fosstodon.org/@jonocarroll">Mastodon</a>.</p>
<br />
<details>
<summary>
<tt>devtools::session_info()</tt>
</summary>
<pre class="bg-success"><code>## ─ Session info ───────────────────────────────────────────────────────────────
## setting value
## version R version 4.1.2 (2021-11-01)
## os Pop!_OS 22.04 LTS
## system x86_64, linux-gnu
## ui X11
## language (EN)
## collate en_AU.UTF-8
## ctype en_AU.UTF-8
## tz Australia/Adelaide
## date 2023-07-07
## pandoc 3.1.1 @ /usr/lib/rstudio/resources/app/bin/quarto/bin/tools/ (via rmarkdown)
##
## ─ Packages ───────────────────────────────────────────────────────────────────
## package * version date (UTC) lib source
## assertthat 0.2.1 2019-03-21 [3] CRAN (R 4.0.1)
## blogdown 1.17 2023-05-16 [1] CRAN (R 4.1.2)
## bookdown 0.29 2022-09-12 [1] CRAN (R 4.1.2)
## bslib 0.4.1 2022-11-02 [3] CRAN (R 4.2.2)
## cachem 1.0.6 2021-08-19 [3] CRAN (R 4.2.0)
## callr 3.7.3 2022-11-02 [3] CRAN (R 4.2.2)
## cli 3.4.1 2022-09-23 [3] CRAN (R 4.2.1)
## crayon 1.5.2 2022-09-29 [3] CRAN (R 4.2.1)
## DBI 1.1.3 2022-06-18 [3] CRAN (R 4.2.1)
## devtools 2.4.5 2022-10-11 [1] CRAN (R 4.1.2)
## digest 0.6.30 2022-10-18 [3] CRAN (R 4.2.1)
## dplyr 1.0.10 2022-09-01 [3] CRAN (R 4.2.1)
## ellipsis 0.3.2 2021-04-29 [3] CRAN (R 4.1.1)
## evaluate 0.18 2022-11-07 [3] CRAN (R 4.2.2)
## fansi 1.0.3 2022-03-24 [3] CRAN (R 4.2.0)
## fastmap 1.1.0 2021-01-25 [3] CRAN (R 4.2.0)
## fs 1.5.2 2021-12-08 [3] CRAN (R 4.1.2)
## generics 0.1.3 2022-07-05 [3] CRAN (R 4.2.1)
## glue 1.6.2 2022-02-24 [3] CRAN (R 4.2.0)
## htmltools 0.5.3 2022-07-18 [3] CRAN (R 4.2.1)
## htmlwidgets 1.5.4 2021-09-08 [1] CRAN (R 4.1.2)
## httpuv 1.6.6 2022-09-08 [1] CRAN (R 4.1.2)
## jquerylib 0.1.4 2021-04-26 [3] CRAN (R 4.1.2)
## jsonlite 1.8.3 2022-10-21 [3] CRAN (R 4.2.1)
## knitr 1.40 2022-08-24 [3] CRAN (R 4.2.1)
## later 1.3.0 2021-08-18 [1] CRAN (R 4.1.2)
## lifecycle 1.0.3 2022-10-07 [3] CRAN (R 4.2.1)
## magrittr 2.0.3 2022-03-30 [3] CRAN (R 4.2.0)
## memoise 2.0.1 2021-11-26 [3] CRAN (R 4.2.0)
## mime 0.12 2021-09-28 [3] CRAN (R 4.2.0)
## miniUI 0.1.1.1 2018-05-18 [1] CRAN (R 4.1.2)
## pillar 1.8.1 2022-08-19 [3] CRAN (R 4.2.1)
## pkgbuild 1.4.0 2022-11-27 [1] CRAN (R 4.1.2)
## pkgconfig 2.0.3 2019-09-22 [3] CRAN (R 4.0.1)
## pkgload 1.3.0 2022-06-27 [1] CRAN (R 4.1.2)
## prettyunits 1.1.1 2020-01-24 [3] CRAN (R 4.0.1)
## processx 3.8.0 2022-10-26 [3] CRAN (R 4.2.1)
## profvis 0.3.7 2020-11-02 [1] CRAN (R 4.1.2)
## promises 1.2.0.1 2021-02-11 [1] CRAN (R 4.1.2)
## ps 1.7.2 2022-10-26 [3] CRAN (R 4.2.2)
## purrr 1.0.1 2023-01-10 [1] CRAN (R 4.1.2)
## R6 2.5.1 2021-08-19 [3] CRAN (R 4.2.0)
## Rcpp 1.0.9 2022-07-08 [1] CRAN (R 4.1.2)
## remotes 2.4.2 2021-11-30 [1] CRAN (R 4.1.2)
## rlang 1.0.6 2022-09-24 [1] CRAN (R 4.1.2)
## rmarkdown 2.18 2022-11-09 [3] CRAN (R 4.2.2)
## rstudioapi 0.14 2022-08-22 [3] CRAN (R 4.2.1)
## sass 0.4.2 2022-07-16 [3] CRAN (R 4.2.1)
## sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.1.2)
## shiny 1.7.2 2022-07-19 [1] CRAN (R 4.1.2)
## stringi 1.7.8 2022-07-11 [3] CRAN (R 4.2.1)
## stringr 1.5.0 2022-12-02 [1] CRAN (R 4.1.2)
## tibble 3.1.8 2022-07-22 [3] CRAN (R 4.2.2)
## tidyselect 1.2.0 2022-10-10 [3] CRAN (R 4.2.1)
## urlchecker 1.0.1 2021-11-30 [1] CRAN (R 4.1.2)
## usethis 2.1.6 2022-05-25 [1] CRAN (R 4.1.2)
## utf8 1.2.2 2021-07-24 [3] CRAN (R 4.2.0)
## vctrs 0.5.2 2023-01-23 [1] CRAN (R 4.1.2)
## xfun 0.34 2022-10-18 [3] CRAN (R 4.2.1)
## xtable 1.8-4 2019-04-21 [1] CRAN (R 4.1.2)
## yaml 2.3.6 2022-10-18 [3] CRAN (R 4.2.1)
##
## [1] /home/jono/R/x86_64-pc-linux-gnu-library/4.1
## [2] /usr/local/lib/R/site-library
## [3] /usr/lib/R/site-library
## [4] /usr/lib/R/library
##
## ──────────────────────────────────────────────────────────────────────────────</code></pre>
</details>
<p><br /></p>