Code management, I/O, dates, errors#

The next category has tools to deal with user defined functions.

Attributes ⎕AT#

User defined functions can have various attributes. For example, they can be niladic/monadic/dyadic/ambivalent, and they of course have an author and a time when they were written. To access this info, we have the attributes system function, ⎕AT:

⎕AT '⎕SE.Dyalog.Utils.formatText'
┌──────┬───────────────────┬─┬──────┐
│1 ¯2 0│2022 7 27 8 39 39 0│0│stefan│
└──────┴───────────────────┴─┴──────┘

The first part, 1 ¯2 0, means that 1: has an result (which is implicitly printed), ¯2: it is ambivalent (the left argument is optional) and 0: it is not an operator. The next part is a timestamp, in ⎕TS form. The third element is the lock state, with 0 for unlocked: APL allows you to lock your code so others cannot inspect and/or suspend it. The last element is the username of whoever last established the function, meaning who most recently made it into an actual function from a text source. It wouldn’t update if the function was copied from a different workspace.

For various practical and/or historical reasons, there are a few different functions that let us inspect code under program control. A user in an interactive session can of course just use the editor.

All these system functions have names in the pattern ⎕xR where x is a single letter.

Canonical representation ⎕CR#

The simplest one is ⎕CR, character/canonical representation. It returns a matrix:

⎕CR '⎕SE.Dyalog.Utils.formatText'
 text←{vals}formatText text;cr;pw;right;hang;first;lead;left            
    ⍝ Format text according to specifications (see ]format -?)          
 :If 900⌶⍬ ⋄ vals←0 ⋄ :EndIf                                            
 text←{(+/∨\' '≠⌽⍵)↑¨↓⍵}∘⎕FMT⍣(1=≡text)⊢text ⍝ convert everything to VTV
 text←↑,/(⊂''),(⊂vals)formatPar¨text                                    

From this you can see on the first line that the function has a result (text) and that the left argument (vals) is optional (it is in braces).

Nested representation ⎕NR#

However, sometimes it is more practical to get the code as a vector of vectors (list of strings), e.g. to extract a single line. For that we have ⎕NR, nested representation:

⎕NR '⎕SE.Dyalog.Utils.formatText' ⍝ first line
 text←{vals}formatText text;cr;pw;right;hang;first;lead;left

Visual representation ⎕VR#

Finally, you may want a single string (with newlines) with all the decorations: ⎕VR, vector/visual Representation:

⎕VR '⎕SE.Dyalog.Utils.formatText'
     ∇ text←{vals}formatText text;cr;pw;right;hang;first;lead;left
[1]       ⍝ Format text according to specifications (see ]format -?)
[2]    :If 900⌶⍬ ⋄ vals←0 ⋄ :EndIf
[3]    text←{(+/∨\' '≠⌽⍵)↑¨↓⍵}∘⎕FMT⍣(1=≡text)⊢text ⍝ convert everything to VTV
[4]    text←↑,/(⊂''),(⊂vals)formatPar¨text
     ∇

Fix ⎕FX#

These three forms are all valid arguments to the function ⎕FX, Fix, which will establish a function according to the code given (or return an index of the first line which was problematic):

3 plus 4  ⎕FX 'r←a plus b' 'r←a+b'
7

Here ⎕FX established the function plus (and returned its name, but we ignored that in favour of 4) and then we used the function right away.

As you may recall, tradfns and dfns can easily define dfns in their code, but they cannot easily define tradfns. ⎕FX lets you dynamically define tradfns should you want to do so.

⎕FX works for dfns too:

3 plus 4  ⎕FX 'plus←{' '⍺+⍵' '}'
7

References ⎕REFS#

Remember the formatText function? It looks complex. Let’s get some order by listing all the identifiers that it uses. Enter References, ⎕REFS:

⎕REFS '⎕SE.Dyalog.Utils.formatText'
cr        
first     
formatPar 
formatText
hang      
lead      
left      
pw        
right     
text      
vals      

Stop, trace ⎕STOP ⎕TRACE#

In the editor, you can set breakpoints (stops) and trace points (output function name, line number and value). You can also do this under program control using ⎕STOP and ⎕TRACE, we cannot demo this in a non-interactive environment. The syntax is simple, though. linenumbers ⎕STOP 'fnname' to set, and omit the left argument to query. Same for ⎕TRACE.

I/O #

You can explicitly request output using ⎕← or ⍞←. ⎕← means print to STDOUT (with trailing newline) and ⍞← means print to STDERR (without trailing newline). However, you can also use these two symbols for input. ←⍞ means read a line from STDIN, and ←⎕ means get a value from STDIN. See character input/output.

will take an APL expression and evaluate it. If you give it an expression without a value, it will keep prompting until you give in (or enter to abort). Expressions evaluated in are not encapsulated, so side-effects will persist (e.g. removing your program!).

Response time limit ⎕RTL#

For normal input, you can also set a response time limit in seconds: ⎕RTL←10 gives the user 10 seconds to respond before a TIMEOUT error is thrown. You can trap this with a dfns error guard {1006::} or a tradfn :Trap 1006.

Enqueue event ⎕NQ#

Enqueue event, ⎕NQ, is mostly used for GUI programming, but there is one other nifty thing you can use it for. The Calendar and DateTimePicker have two methods (functions) called DateToIDN and IDNToDate. But the root object (#, or the APL session itself) also has these methods. These convert between the ⎕TS format (Y M D h m s ms) and a International Day Number (as a float, so it includes the time). These are great for date and time calculations. Two days from now:

32⎕NQ#'IDNTODate',2+2⎕NQ#'DateToIDN'⎕TS
2022 7 29

Don’t worry much about the syntax. ⎕NQ needs 2 as left argument (for this job) and then the # says to look in the root object. At the end is the timestamp/IDN, either appended (,) or juxtaposed. You can also use it to get the weekday:

42⎕NQ#'IDNTODate',2⎕NQ#'DateToIDN'⎕TS
2

0 is Monday.

Read file ⎕NGET#

Dyalog APL has two sets of file handling system functions. One is intended to make it easy to work with Unicode files, the other gives low level control. There are lots of options, but the basic functionality is as follows. To read the contents of a Unicode file, use ⊃⎕NGET 'filename'. This will normalise line breaks to LF (⎕UCS 10). If you’d rather have a list of lines, use ⊃⎕NGET 'filename' 1 instead. This will autodetect encoding and line break style, and should “just work” for almost all files. See docs if you want more fine-grained control.

Write file ⎕NPUT#

Similarly, you can put content into a file with (⊂content) ⎕NPUT 'filename'. If you want to overwrite any existing file, use (⊂content) ⎕NPUT 'filename' 1. Content may be either a simple character vector (string) or a “VTV” (vector of character vectors, i.e. a list of strings). Again, more fine-grained control is available.

Other file system functions ⎕MKDIR ⎕NDELETE ⎕NINFO#

There are also some nice utilities which make it easy to perform some of the most common file operations. You might wonder why not just use ⎕SH/⎕CMD to ask the OS to do it for you? Because various OSs need various commands and syntax. These system functions will let you write truly cross-platform code.

⎕MKDIR and ⎕NDELETE do what you’d think.

⎕NINFO gives you file listings’ info like you’d get from ls/dir, but in a nice array format, perfect for further APL processing.

⍉↑1 0 6⎕NINFO1'/*'
┌─┬─────────────────┬─┐
│1│/home            │0│
├─┼─────────────────┼─┤
│1│/usr             │0│
├─┼─────────────────┼─┤
│1│/bin             │0│
├─┼─────────────────┼─┤
│1│/sbin            │0│
├─┼─────────────────┼─┤
│2│/.file           │1│
├─┼─────────────────┼─┤
│1│/etc             │0│
├─┼─────────────────┼─┤
│1│/var             │0│
├─┼─────────────────┼─┤
│1│/Library         │0│
├─┼─────────────────┼─┤
│1│/System          │0│
├─┼─────────────────┼─┤
│0│/.VolumeIcon.icns│1│
├─┼─────────────────┼─┤
│1│/private         │0│
├─┼─────────────────┼─┤
│1│/.vol            │1│
├─┼─────────────────┼─┤
│1│/Users           │0│
├─┼─────────────────┼─┤
│1│/Applications    │0│
├─┼─────────────────┼─┤
│1│/opt             │0│
├─┼─────────────────┼─┤
│1│/dev             │0│
├─┼─────────────────┼─┤
│1│/Volumes         │0│
├─┼─────────────────┼─┤
│1│/tmp             │0│
├─┼─────────────────┼─┤
│1│/cores           │0│
└─┴─────────────────┴─┘

The first column (indicated by the 1 in the left argument) is the type; 1=directory, 2=file. The second column (0) is the name. The third column (6) is Boolean for whether that item is hidden or not. The ⍠1 indicates that the right argument contains wildcards. Otherwise it would look for a file which had actual question marks and/or stars in its name (normally a bad idea, but at least APL can handle it).

Event number ⎕EN#

In a dfn, you can trap errors with error guards {errornums::result if error try this} and in tradfns with :Trap errornums try this :Case errornum etc. But what are those error numbers? The easiest way to find out is to cause the error and then check event number, ⎕EN, which is a variable that you cannot set directly. It contains the error number of the most recent error.

2{0::⎕EN  ÷}5
0.4

This catches all errors and returns the error number (or the result of the division if no error happened).

2{0::⎕EN  ÷}0
11

Error 11 is DOMAIN ERROR (due to division by zero).

Event message ⎕EM#

⎕EM is a function which takes an error number and gives you the corresponding event message for that event number (⎕EN):

{0::⎕EM ⎕EN  ÷}5
VALUE ERROR

Diagnostic message ⎕DM#

⎕DM (diagnostic message) is a vector of three character vectors; a canonical form of what you see in the session when an error happens:

{0::⎕DM  ÷}5
VALUE ERROR           
      {0::↑⎕DM ⋄ ⍺÷⍵}5
                 ∧    

Extended diagnostic message ⎕DMX#

⎕DMX is a namespace (an object) which has Diagnostic Message (Extended). It has a neat display form with more info:

2{0::⎕DMX  ÷}0
 EM       DOMAIN ERROR
 Message  Divide by zero

We can use ⎕JSON to display all its contents:

2{0::⎕JSON'Compact'0⎕DMX  ÷}0
{
  "Category": "General",
  "DM": [
    "DOMAIN ERROR",
    "      2{0::⎕JSON⍠'Compact' 0⊢⎕DMX ⋄ ⍺÷⍵}0",
    "                                     ∧"
  ],
  "EM": "DOMAIN ERROR",
  "EN": 11,
  "ENX": 1,
  "HelpURL": "https://help.dyalog.com/dmx/18.2/General/1",
  "InternalLocation": [
    "scald.cpp",
    405
  ],
  "Message": "Divide by zero",
  "OSError": [
    0,
    0,
    ""
  ],
  "Vendor": "Dyalog"
}

So this error was thrown on line 387 of scald.cpp.