Tool of Thought

APL for the Practical Man

"Vibe coding since 1987"

What's on the Menu?

June 21, 2025

We find ourselves in need of a menu. Let's noodle around with a design.

With ⎕WC, we had a full-blown object model, and thus MenuBar, Menu, MenuItem and Separator objects with all of their attendant properties and methods. With Abacus components, we are working at a higher level than ⎕WC, trying to make things simple and easy for our purposes. We used to write cover functions over ⎕WC to help construct menus, here we are writing cover functions over HTML. Of course we have to accept the constraints and lack of generality these cover functions impose. We are also trying to get by on the cheap, and not implement a massive dynamic object model, with getters and setters and all that entails.

Our requirements include a classic menu bar, a hamburger menu, and a popup or context-sensitive menu. And sub-menus, checked menu items, and inactive menu items. Separators too. Can we get by with just a Menu component and few functions? Maybe. Let's have a function that builds a menu component that we may then use in different circumstances:

BuildMenu←{
     New←A.Menu.New
     Add←A.Menu.Add
     m←New''
     s←m Add New'File'
     i←s Add'Open' 'Ctrl+O' 'OnFileOpen'
     i←s Add'Save' 'Ctrl+S' 'OnFileSave'
     i←s Add'Save As...' 'Ctrl+A' 'OnFileSaveAs'
     s←m Add New'Edit'
     i←s Add'Cut' 'Ctrl+X' 'OnCut'
     i←s Add'Copy' 'Ctrl+C' 'OnCopy'
     i←s Add'Paste' 'Ctrl+V' 'OnPaste'
     s←m Add New'View'
     i←s Add'List' 'Ctrl+Q' 'OnViewList'
     s2←s Add New'Icons'
     i←s2 Add'Small' 'Ctrl+L' 'OnViewLarge'
     i←s2 Add'Medium' 'Ctrl+M' 'OnViewMedium' 0 0 1
     i←s2 Add'Large' 'Ctrl+S' 'OnViewSmall' 0 0   
     m
 }

The New function of the Menu component creates and returns a new <menu> element. It takes a simple string, the name or label, as its argument.

The Add function adds a menu item or a sub-menu. A menu item is defined by up to 6 items in the argument to Add:

ItemNameDescription
0LabelThe name or label of the menu item
1ShortcutPurely decorative, must be implemented separately
2CallBackThe event handler function (a string)
3SeparatorBoolean; should a divider follow this menu item, default is 0
4ActiveBoolean; active/inactive flag, default is 1
5MarkerInt; 0 for no marker (default), 1 for check, 2 for radio

Usually we eschew long lists of ordered parameters as arguments, but we really want to keep the definition of a menu item on a single line. We can provide a namespace as well, but it will tend to add more clutter:

      m Menu.Add {⍵.Label←'Save as...' ⋄ Callback←'OnSaveAs' ⋄ Active:0 ⋄ ⍵}⎕NS '' 

even in v20 when we can do:

      m Menu.Add (Label:'Save as...' ⋄ Callback:'OnSaveAs' ⋄ Active:0)

When adding a menu item, the result is an <li> element, probably not needed most of the time. When adding a sub menu, the result is a <menu> element.

With these two functions, New and Add, we can construct an entire menu hierarchy.

Neither the New function, nor the user defined Build function adds the menu element and its children to the DOM. This is handled later, on demand, by various functions in the Menu component.

Now that we have a function that builds a menu, we can display it as a popup by attaching a handler to the Javascript contextMenu event. We simply build the menu on demand, and use the Show method to add it to the DOM and display it:

OnContextMenu←{
    m←BuildMenu ⍵
    A.Show m
}

A popup has no permanent visual presence, so this event handler is all we need. The entire menu hierarchy is constructed every time the context menu is called for, and then deleted when the menu is dismissed.

We can take the exact same Build function and use it to implement a menu bar. Unlike a popup menu, a menu bar is permanently visible. We can use the NewMenuBar function to create the menu bar, adding to the DOM, and setting up its behavior, callbacks, etc:

      mb←Parent Menu.NewMenuBar 'Build'

The argument is the name (not the result) of the build function. NewMenuBar will execute this function once to extract the labels from the top level menus, and then will execute the function every time a top level menu item is clicked.

In a similar we we can create a hamburger menu, again from the same source:

      hm←Parent Menu.NewHamburgerMenu 'Build'

If the menu stucture is limited to 2 levels, we can also display it as a sort of ribbon:

      r←Parent Menu.NewRibbon 'Build'

We could add new display types if they arise.

Thus the Menu component provides us with a few functions to construct menus, and then display them in different ways.

A menu only exists in the DOM if the top level is visible; menus are create and deleted on demand.

Grok as APL Interpreter

June 4, 2025

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.

HTML Tables Revisited

February 16, 2025

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

More posts...