Tool of Thought

APL for the Practical Man

"Vibe coding since 1987"

On Arguments

August 15, 2025

In APL we can have at most two arguments to a function, and , left and right.

This is good, because we should never have more than two argument to a function. In fact that is usually one too many. However real life gets in the way, and we often find ourselves in need of more. With nested arrays, namespaces, and simply the interpretation of, say, a simple numeric vector of length 3, the meaning of "one" is in the eye of the beholder.

If we have a vector of expenses as the right argument to a functions that sums them up, we say the right argument, , is a vector of expenses. It's one thing. On the other hand, if we have a function that computes the level payment of a mortgage, it takes a term, a balance, and a rate. Three things. If we pass these as the right argument , we say the function takes three arguments. This is not strictly true. It takes one argument, a vector of three items. Informally we might often speak of 3, 4, 5 or more arguments but we really mean, usually, are distinct items of a possibly nested vector.

Let's use the word parameter to refer to an item of the argument when the argument consists of unique, identifible, nameable, elements.

In our hypothetical mortgage function the first line might look like:

      (b r t)←⍵

Here we have unpacked the argument into 3 parameters b, r and t.

Often we want to have some of the less important parameters be optional and default to a given value. To make the term optional and default to 360 we might do:

      (b r t)←3↑⍵,360

With two or three parameters this technique is manageable, but with more it becomes unwieldy. First, we get a proliferation of usually ad hoc named local variables. Second we can only default trailing parameters.

What can we do about this?

We can take a page from the design of Dyalog's venerable ⎕WC and use named parameters. This is what we have done in Abacus for components. This technique is useful for the pulbic API of libraries that will be used by other programmers. It's probably overkill for private functions.

This technique has many benefits:

We take a strict approach to a vector argument: the tally is always the number of parameters provided. Therefore when providing only one parameter it usually must be enclosed, the exception being a scaler valued parameter provided with no name.

Consider this contrived example:

Sum←{
     p←(
         'One' 1
         'Two' 2
         'Three' 3
         'Four' 4
         'Five' 5
     )Default ⍵
     +/p.(One Two Three Four Five)
 }

First we layout all the parameters, each on its own line, in order, with their default values, using V20 array notation. It is important that we use an array rather than a namespace for the default values, as order matters. This array is passed as the left argument to our Default utility function, which takes the user provided parameters as its right argument:

Default←{
     ⍝ ⍺ ←→ Default name/value pairs
     ⍝ ⍵ ←→ Given argument
     ⍝ ← ←→ A new space with ⍺ overiddden by ⍵
     d←()⎕VSET ⍺
     9=⎕NC'⍵':d ⎕NS ⍵
     n←' '~⍨¨(≢⍵)↑⊃¨⍺
     p←n{(2=≢⍵)∧1≠≡⍵:⍵
         ⍺≡'':⍺
         ⍺ ⍵}¨⍵
     d ⎕NS()⎕VSET p~⊂''
 }

The first thing we do is create a new namespace d with all of the default name/value pairs . This will be our result in all cases.

Then, if we are given a namespace as the right arg, we inject the user supplied names over the default names, and return the space d; we are done. (This uses another nice feature of V20, which finally allows a reference as the left argument to ⎕NS.) Otherwise is a vector where each item is either a value or a name/value pair. If an item is only a value, we assume the name based on its position. These names are then injected into d, overriding defaults, and d is returned.

We can call the Sum function in all of the following ways (We have inserted ⎕←⎕JSON p to see what is going on):

      Sum ''
{"Five":5,"Four":4,"One":1,"Three":3,"Two":2}
15
      Sum 100 ('Five' 50)
{"Five":50,"Four":4,"One":100,"Three":3,"Two":2}
159
      Sum  ('Three' 333)  ('DoesNot' 'Exist')
{"DoesNot":"Exist","Five":5,"Four":4,"One":1,"Three":333,"Two":2}
345
      Sum  (Three:333 ⋄ Two:222)
{"Five":5,"Four":4,"One":1,"Three":333,"Two":222}
565

This Default function does no error checking. Mispelled names (not useful) or additional names (often useful) are happily are accepted. There is no type checking, and no checking for whether or not a parameter is optional. This is fine for the purposes of Abacus, where we assume consenting adults are using the library.

Some checking can be done from the name alone. But if we want to check for mandatory parameters or types, we need to add more items to our default vector. To check for mandatory parameters we can add an optional Boolean:

p←(
         'One' 1 1
         'Two' 2 1
         'Three' 3
         'Four' 4
         'Five' 5
     )

Here we have specified that parameters One and Two are required. Now we need to add some code to the default function to do some checking. We take the approach of adding a new function, rather than mucking up our existing code too much:

Default←{
     ⍝ ⍺ ←→ Default name/value pairs  (Optional 1 for required)
     ⍝ ⍵ ←→ Given argument
     ⍝ ← ←→ A new space with ⍺ overiddden by ⍵
     d←()⎕VSET 2↑¨⍺
     9=⎕NC'⍵':d ⎕NS ⍺ Verify ⍵
     n←' '~⍨¨(≢⍵)↑⊃¨⍺
     p←n{(2=≢⍵)∧1≠≡⍵:⍵
         ⍺≡'':⍺
         ⍺ ⍵}¨⍵
     d ⎕NS ⍺ Verify()⎕VSET p~⊂''
 }

where the Verify function is:

Verify←{
     (n v r)←↓⍉↑3↑¨⍺,¨0
     m←⍵.⎕NL ¯2
     ~∧/m∊⍨r/n:11 ⎕SIGNAL⍨'Required parameter: ',⊃r/n
     0≠≢m~n:11 ⎕SIGNAL⍨'Invalid parameter name: ',⊃m~n
     ⍵
 }

Additional type information could be specified and checked for. Here we begin to run into meta problem of needing parameter names for our parameter specification. Ugh. Let's stay away from that.

Menus Revisited

August 9, 2025

Now that we have a little framework for handling arguments, and have used it for our <input> components, we should probably redesign the Menu component. Let's get rid of the current Add function:

 Add←{
     m←⍺
     9=⎕NC'⍵':m AddSubmenu ⍵
     v←⍵,(≢⍵)↓'' '' '' 0 1 0
     cm←(5⊃v)/⎕UCS 10004
     c←A.New¨{'span'⍵}¨cm(0⊃v)(1⊃v)''
     i←m A.New'li'(A.New'div'c)
     i.(Label Shortcut Function Separator Active Checked)←v
     i.Function←1 A.FQP i.Function
     i.Type←'Item'
     i.tabindex←'0'
     i.Selected←i=⊃m.Content
     i.class←i.Selected/'sel-item'
     i.class,←(~i.Active)/' inactive'
     i.class,←i.Separator/' separator'
     i
 }  

This function adds a menu item or a (sub)menu to a menu. Instead, let's just have an AddItem function to add a menu item, essentially creating a MenuItem component but keeping it all under the Menu namespace:

NewItem←{
     i←(⍺ A.New'li')⎕NS(
         'Label' ''
         'Shortcut' ''
         'Function' ''
         'Separator' 0
         'Active' 1
         'Checked' 0
     )A.InitProps ⍵
     cm←i.Checked/⎕UCS 10004
     c←A.New¨{'span'⍵}¨cm i.Label i.Shortcut''
     i.Content←A.New'div'c
     i.Function←1 A.FQP i.Function
     i.Type←'Item'
     i.tabindex←'0'
     i.Selected←i=⊃⍺.Content
     i.class←i.Selected/'sel-item'
     i.class,←(~i.Active)/' inactive'
     i.class,←i.Separator/' separator'
     i
 }

Much nicer! Clearer to read and see the defaults, much more flexible to call. The Menu.New function can take over adding a (sub)menu to a menu:

New←{
     ⍺←0
     m←(A.New'menu')⎕NS(
         'Label' ''
         'Separator' 0
     )A.InitProps ⍵
     m.Content←m.Label
     m.class←'menu'
     m.popover←'auto'
     m.Toggled←0
     0=⍺:m  ⍝ Top Level menu
     b←'&gt;'
     c←A.New¨{'span'⍵}¨''(m.Content)''(⊂⊂b)
     i←⍺ A.New'li'(A.New'div'(c,m))
     i.Type←'Menu'
     i.tabindex←0
     i.class←m.Separator/'separator'
     i.Selected←0
     m.Content←''
     m
 }
~~~ 

Now we can build a menu like so:

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

Input Components

August 6, 2025

(Or, How Did We Live Before ⎕VSET and ⎕VGET?)

We need some functions to wrap the HTML <input> element. We need inputs for text, numbers, dates check boxes, and pick lists, and thus define the following components:

Let's look at TextInput.New, as a representative pattern:

New←{
     d←(⍺ A.New'div')⎕NS(
         'Name' ''
         'Label' ''
         'Value' ''
         'AutocompleteItems' ''
         'OnChange' ''
     )A.InitProps ⍵
     d.class←'TextInput'
     l←d A.New'label'd.Label
     l.for←d.Name
     i←d A.New'input'
     i.value←d.Value
     i.id←d.Name
     d.Onchange←A.FQP'OnChange'
     d
 }

Here we have taken advantage of V20's new array notation. This allows us to easily see and modify the supported properties and their default values, across multiple lines, without repeated catenation, enclosing, etc. The argument to New may be a namespace, or an array where each item is either a name/value pair, or just a value. If only a value is provided, then its property name is inferred from its position (like ⎕WC). For example, the following two expressions are equivalent:

      TextEdit.New (Label:'First name:' ⋄ Name:'FirstName' ⋄ Value:'Paul')
      TextEdit.New 'FirstName' ('Value' 'Paul') ('Label' 'FirstName)

The InitProps function processes the argument:

InitProps←{
     ⍝ ⍺ ←→ Default name/value pairs
     ⍝ ⍵ ←→ Given argument
     ⍝ ← ←→ A new space with ⍺ overiddden by ⍵
     d←()⎕VSET ⍺
     9=⎕NC'⍵':d ⎕NS ⍵
     n←⊃¨⍺
     m←n↑⍨≢⍵
     p←m{2∧.=(≢⍵),|≡⍵:⍵ ⋄ ⍺ ⍵}¨⍵
     +d ⎕NS()⎕VSET p/⍨n∊⍨⊃¨p
 }

Once we have a namespace with all the user set properties and default properties, all of the property names and values are injected into the root element of the component. This means that property names must begin with an uppercase letter, to avoid conflict with element attributes like class or onclick, and that the names Tag, Content, and Parent are reserved.

If a property is not referenced in the New function, like OnChange, it may also be specified by assignment after the component is created. Setting other properties after the component has been created will generally require a setter function. For example, to set the Value property requires a call to TextInput.SetValue.

The component <div> contains a <label> element and an <input> element. They are tied together explicitly with the for attribute, rather than nesting, in order to provide more options for display purposes, specifically grid and flexbox.

Events

For now, each component accepts an OnChange callback function, to allow further action to be taken after Abacus handles the change. In the case of TextInput, Abacus simply updates the APL DOM to reflect the change in the browser, and then calls the OnChange callback, if specfied by the user:

OnChange←{
     c←⍵.CurrentTarget
     c.Value←⍵.Value
     ⍵ A.Execute'OnChange'
 }

The GetValues and SetValues functions

The main Abacus namespace contains the functions GetValues and SetValues for getting and setting multiple values, a typical task given a dialog box with a bunch of inputs. The GetValues function:

 GetValues←{
     ⍝ ⍵ ←→ DOM node
     ⍝ ← ←→ Namespace of Values from ⍵
     e←⍵ GetElementsWith'Name'
     0=≢e:()
     +()⎕VSET(↑e.Name)e.Value
 }

...takes an APL DOM node as its argument and returns a namespace containing the values for each input component found within. The DOM node is searched for elements that have a Name property, and we assume a corresponding Value property. This search technique could be tightened up if necessary, but for now it's adequate.

The SetValues function:

 SetValues←{
     ⍝ ⍺ ←→ Namespace of values
     ⍝ ⍵ ←→ DOM node
     e←⍵ GetElementsWith'Name'
     0=≢e:0
     m←e.Name
     n v←↓⍉↑⍵ ⎕VGET ¯2
     i j←m n⍳¨⊂m∩n
     0=≢i:0
     c←⎕VGET e[i].class
     0⊣e[i]c.SetValue v[j]
 }

...similarly takes a DOM node as its right argument, but also a namespace of values as its left argment, and populates the inputs in the DOM with the values. SetValues is happy to accept more or fewer values than would be indicated by the DOM, and only set values that have correpsonding inputs. We must use the individual SetValue function in each component to set the values.

More posts...