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.