So far, AI has not been great at writing APL code. But Grok appears pretty good at reading and evaluating APL code. We asked Grok to evaluate this:
40 f 'Once upon a time in the west.'
given:
f←{
⎕IO ⎕ML←0 1
segs←{¯1+⍵{(⍵,⍴⍺)-¯1,⍵}⍸⍵}
split←{((⍵|⍺)>⍳⍵)+⌊⍺÷1⌈⍵}
lf sp←(⎕UCS 13 32)=⊂⍵
sizes←segs lf
⍺←⌈/sizes
blanks←segs~(lf∨sp)/sp
required←blanks+⍺-sizes
breps←required split¨blanks
last←1⌈¯1+⍴sizes
brep←∊(last↑breps),×last↓breps
((~sp)+sp\∊brep)\⍵
}
Grok proceeded to analyze and simulate execution of the function line by line, in gory detail and with voluminous output, explaining every step, all of which appeared to be correct. It got the right answer, and it knew and reported on the value of every variable along the way. It recognized the purpose of the function.
Here is a very small extract of Grok's output:
apl
sizes ← segs lf
lf is all zeros (no line feeds).
segs computes segment lengths:
⍸lf is empty (no 1s).
⍴⍺ = 29 (length of ⍵).
(⍵,⍴⍺)-¯1,⍵ = (empty,29)-¯1,empty = 29-¯1 = 30.
¯1+30 = 29.
So, sizes = 29 (one segment of length 29).
Grok claims it only used its knowledge of APL primitives and syntax to evaluate the function, and that it did not look on the internet to find if this function existed somewhere and get information about it. I think I believe it.
So then we asked Grok to evaluate the expression:
'MM-DD-YYYY' Text2Date '12-31-1999' '02-29-2001' '01-01-2000'
given this code base. It got almost the right answer, getting hung up in the Scale function thinking ⍺-6-⍺
was (⍺-6)-⍺
rather than ⍺-(6-⍺)
, which is a rather odd error given everything else it got right. But that was its only error. Other than that one glitch, it worked though numerous functions and explicated everything in profuse detail.
The last time we looked at tables was way back in 2021. That's a cute little function, and it's useful for relatively small tables, but to put it kindly, it is not particularly efficient.
The Abacus DOM creates a namespace for every element. Tables have lots of elements. This is not good in space:
m←⍕¨{⍵⍴⍳×/⍵}1000 100
t←NewTable m
'CI12' ⎕FMT ⎕SIZE ,¨'mt'
4,800,040
242,324,696
Yikes!
And the time to create is excessive as well. Rendering is slow too. For the Abacus DataGrid component we had to write some special code to get around this as we recreate and re-render the table on every key stroke when scrolling around. A general solution is called for. Rather than creating a namespace for every element, we can create just a few namespaces to hold content values and attribute values:
NewOptiTable←{
t←⎕NS''
t.Tag←'optitable'
t.(Body Header Footer)←{
s←⎕NS''
s.Values←⍵⍴⍨¯2↑1,⍴⍵
s.Rows←⎕NS''
s.Cells←⎕NS''
s}¨3↑⍬ ⍬,⍨(⊂⍣(2=≡⍵))⍵
t
}
This takes hardly any space:
t2←NewOptiTable m
'CI12' ⎕FMT ⎕SIZE ,¨'m' 't' 't2'
4,800,040
242,324,696
4,821,416
And no time, as it doesn't really do anything:
cmpx 'NewOptiTable m' 'NewTable m'
NewOptiTable m → 2.3E¯3 | 0%
* NewTable m → 9.4E¯1 | +41822% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕
(Results are of course different.)
Rendering now requires special code:
RenderOptiTable←{
⍝ ⍵ ←→ OptiTable
o←⊂'Whitespace' 'Preserve'
o,←⊂'UnknownEntity' 'Preserve'
xml←⎕XML⍠o
a←ComposeAttributes ⍵
xml 0 'table' ''a⍪⊃⍪/'thead' 'tbody' 'tfoot'{
r c←⍴⍵.Values
0∊r,c:0 4⍴0
n←r+r×c
m←n 2⍴1 c⌿2 2⍴2 'tr' 3((⍺≡'tbody')⊃'th' 'td')
m,←,(⊂''),⍵.Values
a←,⊃,/r(r c){
d←⍺
n←(⎕C ⎕A)⍵.⎕NL ¯2
0=≢n:d⍴⊂0 2⍴⊂''
v←⍵⍎¨n
n←'-'@('_'∘=)¨n
b←{80=⎕DR⊃⊃⍵}¨v
a←{
0=∨/b:d⍴⊂0 2⍴⊂''
k←↑{d⍴⊆⍵}¨b/v
(r q)←0 ¯1+⍴⍴k
⊂[0 r](b/n),⍤0⍤0 q⊢k ⍝ Hat tip: AB
}0
a⊣(n/⍨~b){
' '=⊃⍺:0
n i v←↓⍉(⊂⍺),↑⍵
0⊣a[i]←a[i]⍪¨↓⍉↑n v
}¨v/⍨~b
}¨⍵.Rows ⍵.Cells
t←ComposeAttributes ⍵
1 ⍺''t⍪m,a
}¨⍵.(Header Body Footer)
}
But is much faster:
cmpx 'RenderOptiTable t2' 'DOM2HTML t'
RenderOptiTable t2 → 2.7E¯2 | 0% ⎕
DOM2HTML t → 1.5E0 | +5441% ⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕
The argument to NewOptiTable
is identical to NewTable
, but specifying attributes is done differently:
t←NewOptiTable (⍕¨3 2⍴⍳6) ('One' 'Two')
t.class←'table-class'
t.Header.class←'header-class'
t.Header.Cells.class←'col1' 'col2'
t.Body.Rows.id←'rowid1' 'rowid2' 'rowid3'
t.Body.Cells.class←'cell-class'
t.Body.Cells.id←'id'∘,¨⍕¨3 2⍴⍳6
t.Body.Cells.onclick←((0 0) 'foo1') ((2 1) 'foo2')
Attributes are specified by assignment in the root for the table
element, and in the Header
, Body
, and Footer
subspaces for the thead
, tbody
, and tfoot
elements respectively. Each of these 3 subspaces contain a Rows
and Cells
subspace for attributes for tr
and either td
or th
elements as appropriate. The only purpose of the Rows
and Cells
subspaces is to have place to specify attributes.
Attributes are enclosed if simple, and reshaped to match the target elements in question, providing something analagous to scalar extension. (For example, to specfiy a class per column in the above example simply do t.Body.Cells.class←'c1' 'c2'
.) Alternatively, attributes may be specified by explicit index. So the above produces:
RenderOptiTable t
<table class="table-class">
<thead class="header-class">
<tr>
<th class="col1">One</th>
<th class="col2">Two</th>
</tr>
</thead>
<tbody>
<tr id="rowid1">
<td class="cell-class" id="id0" onclick="foo1">0</td>
<td class="cell-class" id="id1">1</td>
</tr>
<tr id="rowid2">
<td class="cell-class" id="id2">2</td>
<td class="cell-class" id="id3">3</td>
</tr>
<tr id="rowid3">
<td class="cell-class" id="id4">4</td>
<td class="cell-class" id="id5" onclick="foo2">5</td>
</tr>
</tbody>
</table>
An optitable
element cannot be added to the DOM like a regular element. It must be rendered first and then double enclosed. So to add as a child to some element e
:
e.Content←⊂⊂RenderOptiTable t
In the previous post we played around with the new system functions ⎕VSET
and ⎕VGET
, and noted a couple of unfortunate, though perhaps unavoidable, design decisions. First is having to enclose when setting or getting a single name, and second is having to provide a matrix of names when the argument is two separate arrays of names and values, rather than name and value pairs.
What if we simplify (and sacrifice some) things by insisting the argument is always two items, a list of names and a list of values, defining ⎕VSET
as:
VSET←{
⍺←⎕THIS
n v←⍵
2=|≡n:⍺ ⎕VSET(↑n)v
2=⍴⍴n:⍺ ⎕VSET n v
⍺ ⎕VSET ⊂n v
}
And ⎕VGET
as:
VGET←{
⍺←⎕THIS
1=≢⊆⍵:⍺ ⎕VGET⊃⊆⍵
n v←⍵
(1=⍴⍴n)∧2>|≡n:⍺ ⎕VGET ⊂⍵
⍺ ⎕VGET (↑n) v
}
These functions do not allow name and value pairs, and as a corrollary do not allow the provision of only some default values for ⎕VGET
. It's all or nothing with respect to default values.
However, we can do:
s←()VSET 'One' 1
s.One
1
And:
n←'One' 'two' 'Three'
v←1 2 3
s←()VSET n v
or:
s←()VSET (↑n) v
Similarly for getting values, we can avoid an enclose:
s VGET 'One'
1
s VGET 'Four' 4
4
But for multiple names only we need to enclose:
s VGET ⊂'One' 'Two'
1 2
s VGET ⊂n
1 2 3
And if providing default values, we must provide them all:
s VGET ('One' 'Five') (1 5)
1 5
One nice thing about this design is the simplicity of documenting it. We simply say the right argument is composed of two items, names and values. We don't need to explain that if there are two elements, and the first element is a matrix, then things are interpreted one way, but if the first element is a vector then it means somehthing else. This kind of design always makes us feel a bit uneasy.
The loss of being able to default only some values and not all for a given set of names is not much to give up. The structure:
(Name Value) Name (Name Value)...
does not arise very naturally in code, though it may as a literal structure.
Regardless of all of this, it is easy enough to cover ⎕VGET
and ⎕VSET
as we do above to get the behavior we want, if indeed that is what we want, and arguably the reverse would not be true.