Dyalog 20.0 Programming Guide
Dyalog 20.0 Programming Guide
Reference Guide
Unless stated otherwise, all examples in this document assume that ⎕IO ⎕ML ← 1
email: support@[Link]
[Link]
TRADEMARKS:
Contents
1 Introduction ................................................................................................................... 14
1 Introduction
1.1 Programmers Guide Introduction
1.1.1 Workspaces
APL expressions are evaluated within a workspace. The workspace may contain objects,
namely classes, namespaces, operators, functions and variables defined by the user.
APL expressions may include references to primitive operators, functions and variables
provided by APL. These objects do not reside in the workspace, but space is required
for the actual process of evaluation to accommodate temporary data. During
execution, APL records the state of execution through the STATE INDICATOR which is
dynamically maintained until the process is complete. Space is also required to identify
objects in the workspace in the SYMBOL TABLE. Maintenance of the symbol table is
entirely dynamic. It grows and contracts according to the current workspace contents.
Workspaces may be explicitly saved with an identifying name. The workspace may
subsequently be loaded, or objects may be selectively copied from a saved workspace
into the current workspace.
Workspaces are stored in files whose names must conform to operating system
conventions. When a workspace name is specified without a file suffix, these are added
or implied. For further information, see Installation/Configuration: Wsext.
If the name of the file in which the workspace is saved contains spaces, the ws
argument for the system functions )SAVE, )COPY, )PCOPY, )LOAD, )XLOAD and )DROP
should be surrounded by two double-quote (") characters. To include a " character in
the file name, you must specify two adjoining double-quotes (that is, """"). Note
however that Windows does not allow double-quotes in file names, so this effectively
applies only to non-Windows systems.
Examples
The above statement fails because the presence of the space in the file name requires
that it be surrounded by "s.
ABCDEFGHIJKLMNOPQRSTUVWXYZ_
abcdefghijklmnopqrstuvwxyz
ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝß
àáâãäåæçèéêëìíîïðñòóôõöøùúûüþ
0123456789
∆⍙
ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ
Note that using a standard Unicode font (rather than APL385 Unicode used in the table
above), the last row above would appear as the circled alphabet, Ⓐ to Ⓩ.
Examples
Legal Illegal
THIS∆IS∆A∆NAME BAD NAME
X1233 3+21
SALES S!H|PRICE
pjb_1 1_pjb
1.1.3 Arrays
Arrays
An array has two properties; structure and data type. Structure is identified by rank,
shape, and depth.
Rank
An array may have 0 or more axes or dimensions. The number of axes of an array is
known as its rank. Dyalog APL supports arrays with a maximum of 15 axes.
Shape
Each axis of an array may contain zero or more items. The number of items along each
axis of an array is called its shape. The shape of an array is itself a vector. Its first item is
the length of the first axis, its second item the length of the second axis, and so on. An
array, whose length along one or more axes is zero, is called an empty array.
Depth
An array whose items are all simple scalars (that is, single numbers, characters or refs)
is called a simple array. If one or more items of an array is not a simple scalar (that is, is
another array, or a ⎕OR), the array is called a nested array. A nested array may contain
items which are themselves nested arrays. The degree of nesting of an array is called its
depth. A simple scalar has a depth of 0. A simple vector, matrix, or multi-dimensional
array has depth 1. An array whose items are all depth 1 subarrays has depth 2; one
whose items are all depth 2 subarrays has depth 3, and so forth.
Type
An array, whose elements are all numeric, is called a numeric array; its TYPE is numeric.
A character array is one in which all items are characters. An array whose items contain
both numeric and character elements is of MIXED type.
Numbers
Real Numbers
Numbers are entered or displayed using conventional decimal notation (for example,
299792.458) or using a scaled form for example, 2.999792458E5).
Example
Negative numbers are preceded by the high minus (¯) symbol, not to be confused with
the minus (-) function. In scaled form, both the mantissa and the scale may be
negative.
Example
Complex Numbers
Complex numbers use the J notation introduced in IBM APL2 and are written as aJb or
ajb (without spaces) where the real and imaginary parts a and b are written as
described above. The capital J is always used to display a value.
Examples
2+¯1*.5
2J1
.3j.5
0.3J0.5
1.2E5J¯4E¯4
120000J¯0.0004
Zilde
The empty vector (⍳0) may be represented by the numeric constant ⍬ called ZILDE.
Characters
Characters are entered within a pair of APL quotes. The surrounding APL quotes are not
displayed on output. The APL quote character itself must be entered as a pair of APL
quotes.
Examples
'DYALOG APL'
DYALOG APL
'*'
*
Enclosed Elements
An array may be enclosed to form a scalar element through any of the following means:
Examples
(⊂1 2 3),⊂'ABC'
1 2 3 ABC
(1 2 3) 'ABC'
1 2 3 ABC
⍳2 3
1 1 1 2 1 3
2 1 2 2 2 3
Specification of Variables
Examples
X Y←'ONE' 'TWO'
X
ONE
Y
TWO
Vector Notation
Vector notation complements Section [Link] with a clean syntax for vectors.
A series of two or more adjacent expressions results in a vector whose elements are
the enclosed arrays resulting from each expression. This is known as vector (or strand)
notation. Each expression in the series may consist of one of the following:
Examples
⍴A←2 4 10
3
⍴TEXT←'ONE' 'TWO'
2
Y ← (2+2) 'IS' 4
Y
4 IS 4
A B C ←→ (⊂A), (⊂B), ⊂C
Array Notation
Array notation extends Section [Link] to define arrays of higher rank, and namespaces,
and lets these definitions span multiple lines:
Examples
Nested Vector
Matrix
⍴m←[0 6 1 8 ⋄ 1 4 1 4
2 7 1 8 ⋄ 3 1 4 2]
4 4
m
0 6 1 8
1 4 1 4
2 7 1 8
3 1 4 2
Short items are padded. (See Dyalog APL Language: Mix for details.)
⍴mice←['Three'
'Blind'
'Mice']
3 5
mice,'|'
Three|
Blind|
Mice |
⍴RC←[0 'OK'
1 'WS FULL'
2 'SYNTAX ERROR'
3 'INDEX ERROR'
4 'RANK ERROR']
5 2
⍴expenses←0⌿[ ⍝ typed template matrix
'Glasgow' 125.84
]
0 2
Column Matrix
[ 1 ⋄ 2 ⋄ 3 ]
1
2
3
⍴¨cm3←[('Three' ⋄)
('Blind' ⋄)
('Mice' ⋄)]
5
5
4
cm3
┌─────┐
│Three│
├─────┤
│Blind│
├─────┤
│Mice │
└─────┘
Rank-3 Array
⍴block←[
[3 1 4 ⋄ 1 5 ]
[2 7 0 ⋄ 2]
]
2 2 3
block
3 1 4
1 5 0
2 7 0
2 0 0
Namespaces
Array notation allows you to write a namespace literal as zero or more name-value
pairs, spanned by parentheses.
() ⍝ empty namespace
#.[Namespace]
()() ⍝ vector of empty namespaces
#.[Namespace] #.[Namespace]
( () ⋄ () ) ⍝ vector of empty namespaces
#.[Namespace] #.[Namespace]
n←(x:'hello')
n.x
hello
m←(x:['hello'
'world'])
⍴≢m.x ⍝ matrix
2
(
FirstName:'Wolfgang'
LastName:'Mozart'
Age:35
)
#.[Namespace]
long←'bobby'
short←'jack'
ns←(short:'jill' ⋄ inner:short∘.=short←3↑long)
[Link]
1 0 1
0 1 0
1 0 1
short ⍝ altered by inner assignment
bob
[Link] ⍝ unaffected by inner assignment
jill
(y:(x:'hello')).y ⍝ inner namespace's parent is NOT outer
namespace
#.[Namespace]
Specification
The new syntactic forms were previously errors in every mainstream APL
implementation and therefore introduce no backward incompatibilities.
In the following:
Namespace
Vector
Short elements are padded to fill, and scalars are treated as length-1 vectors.
Information
({1=⍵:'y' ⋄ 'n'}?2)
the diamond is part of the dfn and does not break the surrounding parenthesis.
Unsupported
While one can include the last three items when writing in array notation, they cannot
be displayed in the notation.
Formal Syntax
The array notation can be described in this form, where expression is any traditional
APL expression:
Structuring of Arrays
Examples
2 2 4⍴'ABCDEFGHIJKLMNOP'
ABCD
EFGH
IJKL
MNOP
,m ⍝ ravel
1 2 3 4
1 2 3,4 ⍝ catenate
1 2 3 4
1⌽1 2 3 4 ⍝ rotate
2 3 4 1
⌽m ⍝ reverse
2 1
4 3
⊖m ⍝ reverse first
3 4
1 2
⍉m ⍝ transpose
1 3
2 4
↑(1 2 3)(4) ⍝ mix
1 2 3
4 0 0
↓2 4⍴'COWSHENS' ⍝ split
┌────┬────┐
│COWS│HENS│
└────┴────┘
[ 1 2 3 ⋄ (4 5) 6 (7 8 9)]
┌───┬─┬─────┐
│1 │2│3 │
├───┼─┼─────┤
│4 5│6│7 8 9│
└───┴─┴─────┘
∊[ 1 2 3 ⋄ (4 5) 6 (7 8 9)] ⍝ enlist
1 2 3 4 5 6 7 8 9
≢⎕←⊂1 2 3 4 ⍝ enclose
┌───────┐
│1 2 3 4│
└───────┘
1
2 0 1 3 0 2 0 1⊂'abcdefg' ⍝ partitioned enclose
┌┬──┬─┬┬┬──┬┬──┬┐
││ab│c│││de││fg││
└┴──┴─┴┴┴──┴┴──┴┘
1 1 2 2 2⊆1 2 3 4 5 ⍝ partition
┌───┬─────┐
│1 2│3 4 5│
└───┴─────┘
Display of Arrays
Simple scalars and vectors are displayed in a single line beginning at the left margin. A
number is separated from the next adjacent element by a single space.
3
3
⍳4
1 2 3 4
Print Precision
The Dyalog APL Language: Pp system variable determines the number of significant
digits to be printed. The fractional part of the number is rounded in the last digit if it
cannot be represented within the print precision. Leading zeros, and trailing zeros after
a decimal point, are omitted, as are decimal points for integers.
÷3 2 6
0.3333333333 0.5 0.1666666667
⎕PP←3
Simple Matrices
Simple matrices are displayed in rectangular form, with one line for each matrix row.
2 4⍴'HANDFIST'
HAND
FIST
1 2 3 ∘.× 6 2 5
6 2 5
12 4 10
18 6 15
All elements in a column are displayed in the same format, but the format and width
for the column is determined independently of other columns.
A numeric column aligns any decimal points or E characters (for scaled formats), adding
trailing zeros to the mantissae if necessary. Integers are right-adjusted one place to the
left of any decimal points.
Non-simple Arrays
In the display of non-simple arrays, each element is displayed within a rectangle such
that the rows and columns of the array are aligned. Simple items within the array are
displayed as above. For non-simple items, this rule is applied recursively, with one
space added on each side of the enclosed element for each level of nesting.
⍳3
1 2 3
⊂⍳3
1 2 3
⊂⊂⍳3
1 2 3
Multi-dimensional Arrays
2 3 4⍴⍳24
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20
21 22 23 24
3 1 1 3⍴'THEREDFOX'
THE
RED
FOX
The power of this form of display is made apparent when formatting informal reports.
SALES←[
50 5.25 75
250 20.15 900
500 80.98 650
1000 90.03 1200
]
Array Notation
Arrays (including namespaces) can be displayed in the session using Section [Link].
This mode is enabled using the ][Link] user command. For example:
SALES
50 5.25 75
250 20.15 900
500 80.98 650
1000 90.03 1200
][Link] ON
'Was OFF'
SALES
[
50 5.25 75
250 20.15 900
500 80.98 650
1000 90.03 1200
]
In the User Interface it can also be toggled on and off using the
To enable the display of output using array notation when starting a Dyalog session, set
the Installation/Configuration: Aplan For Output configuration parameter to 1.
Print Width
If the display of an array is wider than the print width, as defined by the Dyalog APL
Language: Pw system variable, it will be folded at or before ⎕PW and the folded portions
indented six spaces. The display of a simple numeric or mixed array may be folded at a
width less than ⎕PW so that individual numbers are not split across a page boundary.
⎕PW←40
?3 20⍴100
54 22 5 68 68 94 39 52 84 4 6 53 68
85 53 10 66 42 71 92 77 27 5 74 33 64
66 8 64 89 28 44 77 48 24 28 36 17 49
1 39 7 42 69 49 94
76 100 37 25 99 73 76
90 91 7 91 51 52 32
The user command ]Disp illustrates the specified array, with borders indicating sub-
array shape and type. For example:
]Disp 'ABC' [1 2 3 4 ⋄]
┌→──┬───────┐
│ABC│1 2 3 4↓
└──→┴~─────→┘
This is similar to displaying array with ]Boxing on -style=mid (see The ]Boxing User
Command below).
An explanation of the symbols that appear in the borders can be seen by running ]Disp
-??
The user command ]Display illustrates the specified array, with borders indicating
array and sub-array shape and type. For example:
]Display 'ABC' [1 2 3 4 ⋄]
┌→────────────────┐
│ ┌→──┐ ┌→──────┐ │
│ │ABC│ ↓1 2 3 4│ │
│ └───┘ └~──────┘ │
└∊────────────────┘
This is similar to displaying array with ]Boxing on -style=max (see The ]Boxing User
Command below).
An explanation of the symbols that appear in the borders can be seen by running
]Display -??
The user command ]Boxing changes how nested arrays are displayed in the Session.
The following examples show different settings.
]Boxing on -style=min
Was OFF -style=min
'ABC' [1 2 3 4 ⋄]
┌───┬───────┐
│ABC│1 2 3 4│
└───┴───────┘
]Boxing on -style=mid
Was ON -style=min
'ABC' [1 2 3 4 ⋄]
┌→──┬───────┐
│ABC│1 2 3 4↓
└──→┴~─────→┘
]Boxing on -style=max
┌→────────────────┐
│Was ON -style=mid│
└─────────────────┘
'ABC' [1 2 3 4 ⋄]
┌→────────────────┐
│ ┌→──┐ ┌→──────┐ │
│ │ABC│ ↓1 2 3 4│ │
│ └───┘ └~──────┘ │
└∊────────────────┘
]Boxing on -style=min
Was ON -style=max
]Boxing off
Was ON
'ABC' [1 2 3 4 ⋄]
ABC 1 2 3 4
Information about all the options and explanation of the symbols that appear in the
borders can be seen by running ]Display -??
Every array has an associated prototype which is derived from the array's first item.
If the first item is a number, the prototype is 0. Otherwise, if the first item is a
character, the prototype is ' '(space). Otherwise, if the first item is a (ref to) an
instance of a Class, the prototype is a ref to that Class.
Otherwise (in the nested case, when the first item is other than a simple scalar), the
prototype is defined recursively as the prototype of each of the array's first item.
Examples
Array Prototype
1 2 3.4 0
99 'b' 66 0
(1 2)(3 4 5) 0 0
Fill Items
Fill items for an overtake operation, are derived from the argument's prototype. For
each 0 or ' ' in the prototype, there is a corresponding 0 or ' ' in the fill item and for
each class reference in the prototype, there is a ref to a (newly constructed and
distinct) instance of that class that is initialised by the niladic (default) constructor for
that class, if defined.
Examples
4↑1 2
1 2 0 0
4↑'ab'
ab
4↑(1 2)(3 4 5)
1 2 3 4 5 0 0 0 0
2↑⎕NEW MyClass
#.[Instance of MyClass] #.[Instance of MyClass]
In the last example, two distinct instances are constructed (the first by ⎕NEW and the
second by the overtake).
K-Cells
A rank-k cell or k-cell of an array are terms used to describe a sub-array on the last k
axes of the array. Negative k is interpreted as r+k where r is the rank of the array, and
is used to describe a sub-array on the leading |k axes of an array.
If X is a 3-dimensional array of shape 2 3 4, the 1-cells are its 6 rows each of 4 elements;
and its 2-cells are its 2 matrices each of shape 3 4. Its 3-cells is the array in its entirety.
Its 0-cells are its individual elements.
Major Cells
The major cells of an array X is a term used to describe the sub-arrays on the leading
dimension of the array X with shape 1↓⍴X. Using the k-cell terminology, the major cells
are its ¯1-cells.
The major cells of a vector are its elements (0-cells). The major cells of a matrix are its
rows (1-cells), and the major cells of a 3-dimensional array are its matrices along the
first dimension (2-cells).
Examples
In the following, the major cells of A are 1979, 1990, 1997, 2007, and 2010; those of B
are 'Thatcher', 'Major', 'Blair', 'Brown', and 'Cameron'; and those of C are the
four 2-by-3 matrices.
A
1979 1990 1997 2007 2010
B
Thatcher
Major
Blair
Brown
Cameron
⍴B
5 8
⎕←C←4 2 3⍴⍳24
0 1 2
3 4 5
6 7 8
9 10 11
12 13 14
15 16 17
18 19 20
21 22 23
Using the k-cell terminology, if r is the rank of the array, its major cells are its r-1-cells.
Note that if the right operand k of the Rank Operator ⍤ is negative, it is interpreted as
0⌈r+k. Therefore the value ¯1 selects the major cells of the array.
1.1.4 Expressions
An expression is a sequence of one or more syntactic tokens which may be symbols or
constants or names representing arrays (variables) or functions. An expression which
produces an array is called an ARRAY EXPRESSION. An expression which produces a
function is called a FUNCTION EXPRESSION. Some expressions do not produce a result.
Examples
X←2×3-1
2×3-1
4
(2×3)-1
5
Blanks or parentheses are not needed to separate primitive functions from names or
constants, but they are permitted:
-2 ←→ (-)(2) ←→ (-) 2
Blanks or parentheses are not needed to separate operators from primitive functions,
names or constants. They are permitted with the single exception that a dyadic
operator must have its right argument available when encountered. The following
syntactical forms are accepted:
+(.)× or (+.)×
1.1.5 Functions
A function is an operation which is performed on zero, one or two array arguments and
may produce an array result. Three forms are permitted:
Functions have long SCOPE on the right; that is, the right argument of the function is
the result of the entire expression to its right which must be an array. A dyadic function
has short scope on the left; that is, the left argument of the function is the array
immediately to its left. Left scope may be extended by enclosing an expression in
parentheses whence the result must be an array.
For some functions, the explicit result is suppressed if it would otherwise be displayed
on completion of evaluation of the expression. This applies on assignment to a variable
name. It applies for certain system functions, and may also apply for defined functions.
Examples
10×5-2×4
¯30
2×4
8
5-8
¯3
10ׯ3
¯30
(10×5)-2×4
42
Defined Functions
Functions may be defined with the system function ⎕FX, or with the function editor. A
function consists of a HEADER which identifies the syntax of the function, and a BODY
in which one or more APL statements are specified.
The header syntax identifies the function name, its (optional) result and its (optional)
arguments. If a function is ambivalent, it is defined with two arguments but with the
left argument within braces ({}). If an ambivalent function is called monadically, the
left argument has no value inside the function. If the explicit result is to be suppressed
for display purposes, the result is shown within braces. A function need not produce an
explicit result. Refer to Chapter 2 for further details.
Example
∇ R←{A} FOO B
[1] R←⊃'MONADIC' 'DYADIC'[⎕IO+0≠⎕NC'A']
[2] ∇
FOO 1
MONADIC
Examples
PLUS←+
PLUS
+
SUM←+/
SUM
+/
Function expressions may include defined functions and operators. These are displayed
as a ∇ followed by their name.
Example
MEAN
∇MEAN
AVERAGE←MEAN
AVERAGE
∇MEAN
AVG←MEAN∘,
AVG
∇MEAN ∘,
1.1.6 Operators
An operator is an operation on one or two operands which produces a function called a
DERIVED FUNCTION. An operand may be a function or an array. Operators are not
ambivalent. They require either one or two operands as applicable to the particular
operator. However, the derived function may be ambivalent. The derived function need
not return a result. Operators have higher precedence than functions. Operators have
long scope on the left. That is, the left operand is the longest function or array
expression on its left. The left operand may be terminated by:
Examples
⍴∘⍴¨X
1 1 1
(⍴∘⍴)¨X
1 1 1
⎕∘←∘⎕VR¨'PLUS' 'MINUS'
∇ R←A PLUS B
[1] R←A+B
∇
∇ R←A MINUS B
[1] R←A-B
∇
PLUS/1 2 3 4
10
Defined Operators
Operators may be defined with the system function ⎕FX, or with the function editor. A
defined operator consists of a HEADER which identifies the syntax of the operator, and
a BODY in which one or more APL statements are specified.
A defined operator may have one or two operands; and its derived function may have
one or two arguments, and may or may not produce a result. The header syntax
defines the operator name, its operand(s), the argument(s) to its derived function, and
the result (if any) of its derived function. The names of the operator and its operand(s)
are separated from the name(s) of the argument(s) to its derived function by
parentheses.
Example
The above example shows a dyadic operator called AND with two operands (F and G).
The operator produces a derived function which takes two arguments (A and B), and
produces a result (R).
12 +AND÷ 4
16 3
12 (3 AND 5) 4
12 3 4 12 5 4
12 (× AND 5) 4
48 12 5 4
Y
A F H MOP DOP DOT IDX
A 6A 3 AF 3 AF 4F 7 REF 4A
F 2A 1F 4F 4F 4F
H 1F 4F 4F 4H
AF 2A 1F
MOP 4 ERR
X
DOP 5 MOP 5 MOP 5 MOP
JOT 5 MOP 5 MOP 5 MOP 4F
DOT 6 ERR 5 MOP 5 MOP 6 ERR
REF 7A 7F 7H 7 MOP 7 DOP
IDX 3 ERR 3 ERR 3 ERR
where
In this table:
For example, in the expression a b.c[d], where a, b, c and d are arrays, the binding
proceeds:
a b . c [d]
6 7 6 4 ⍝ binding strengths between entities
→ a (b.) c [d]
0 7 4
→ a (b.c) [d]
6 4
→ (a(b.c))[d]
Introduction
Note that the right-most item of a function train (which is by definition a function)
must be isolated from anything to its right, otherwise it will be bound to that rather
than to the items to its left. This is done using parentheses.
For example, the following expression comprises a function train -,÷ that is separated
from its argument 2 by parentheses:
(-,÷) 2
¯2 0.5
and means:
Whereas, without the parentheses to identify the function train, the expression means
(as it did before):
-,÷ 2
¯0.5
The following trains are currently supported where f, g and h are functions and A is an
array:
f g h
A g h
g h
The 3-item trains (f g h) and (A g h) are termed forks while the 2-item train (g h)
is termed an atop. To distinguish the two styles of fork, we can use the terms fgh-fork
or Agh-fork.
Trains as Functions
A train is syntactically equivalent to a function and so, in common with any other
function, may be:
In particular, trains may be applied to a single array (monadic use) or between 2 arrays
(dyadic use), providing six new constructs.
Identifying a Train
(-,÷)5
¯5 0.2
negrec←-,÷
negrec 5
¯5 0.2
Whereas, without these means to identify the sequence as a train, the expression:
-,÷ 5
¯0.2
Idiom Recognition
Function trains lend themselves to idiom recognition, a technique used to optimise the
performance of certain expressions.
Example
X←?1e6⍴1e6
(X≥999000)⍳1
1704
X (⍳∘1 ≥) 999000
1704
Trains of Trains
Examples
6( +,-,×,÷)2 ⍝ fork:(6+2),((6-2),((6×2),(6÷2)))
8 4 12 3
]boxing on
Was OFF
]boxing -trains=tree
Was -trains=box
+,-,×,÷ ⍝ boxed (tree) display of fork
┌─┼───┐
+ , ┌─┼───┐
- , ┌─┼─┐
× , ÷
Binding Strengths
The binding strength between the items of a train is less than that of operand-operator
binding. In other words, operators bind first with their function (or array) operands to
form derived functions, which may then participate as items in a train.
Example
This means that any of the four hybrid tokens / ⌿ \ ⍀ will not be interpreted as a
function if there's a function to its left in the train. In order to fix one of these tokens as
a replicate or expand function, it must be isolated from the function to its left:
(⍳(/∘⊢)⍳)3 ⍝ → (⍳3)/⊢(⍳3)
1 2 2 3 3 3
(2/⍳)3 ⍝ Agh-fork is OK
1 1 2 2 3 3
In the case of ⍳, the principal argument is the one on the left and in the case of ∊, it is
the one on the right. The following table shows the principal (P) and subject (s)
arguments for each of the functions.
P ⍳ s Index of
s ∊ P Membership
s ∩ P Intersection
P ∪ s Union
s ~ P Without
P {(↓⍺)⍳↓⍵} s Matrix Iota (idiom)
P∘⍋ and P∘⍒ Grade
The Dyalog APL implementation of these functions already uses a technique known as
hashing to improve performance over a simple linear search. (Note that ⍷ (find) does
not employ the same hashing technique, and is excluded from this discussion.)
Building a hash table for the principal argument takes a significant time but is rewarded
by a considerably quicker search for each item in the subject. Unfortunately, the hash
table is discarded each time the function completes and must be reconstructed for a
subsequent call (even if its principal argument is identical to that in the previous one).
For optimal performance of repeated search operations, the hash table may be
retained between calls, by binding the function with its principal argument using the
primitive ∘ (compose) operator. The retained hash table is then used directly whenever
this monadic derived function is applied to a subject argument.
Notice that retaining the hash table pays off only on a second or subsequent
application of the derived function. This usually occurs in one of two ways: either the
derived function is named for later (and repeated) use, as in the first example below or
it is applied repeatedly as the operand of a primitive or defined operator, as in the
second example.
Idiom Recognition
Idioms are commonly used expressions that are recognised and evaluated internally,
providing a significant performance improvement.
For example, the idiom BV/⍳⍴A (where BV is a Boolean vector and A is an array) would
(in earlier Versions of Dyalog APL) have been evaluated in 3 steps as follows:
In the current Version of Dyalog APL, the expression is recognised in its entirety and
processed in a single step as if it were a single primitive function. In this case, the
resultant improvement in performance is between 2 and 4.5.
Idiom recognition is precise; an expression that is almost identical but not exactly
identical to an expression given in the Section [Link] table will not be recognised.
For example, ⎕AV⍳ will be recognised as an idiom, but (⎕AV)⍳ will not. Similarly, (,)/
would not be recognized as the Join idiom.
Idiom List
In the following table, arguments to the idiom have types and ranks as follows:
For example: NV: numeric vector, CM: character matrix, PV: nested vector.
Idiom Description
⍴⍴XA The rank of XA as a 1-element vector
≢⍴XA The rank of XA as a scalar
BV/⍳NS The subset of NS corresponding to the 1s in BV
BV/⍳⍴XV The positions in XV corresponding to the 1s in BV
The subset of XV in the index positions defined by NA
NA⊃¨⊂XV
(equivalent to XV[NA] )
XA1{}XA2 XA1 and XA2 are ignored (no result produced)
XA1{⍺ ⍵}XA2 XA1 and XA2 as a two item vector (XA1 XA2)
{0}XA 0 irrespective of XA
{0}¨XA 0 corresponding to each item of XA
The enclose of the items of PV (which must be of depth 2)
,/PV
catenated along their last axes
The enclose of the items of PV (which must be of depth 2)
⍪/PV
catenated along their first axes
⊃⌽XA The item in the top right of XA ( ⎕ML<2 )
↑⌽XA The item in the top right of XA ( ⎕ML≥2 )
⊃⌽,XA The item in the bottom right of XA ( ⎕ML<2 )
↑⌽,A The item in the bottom right of XA ( ⎕ML≥2 )
0=⍴XV 1 if XV has a shape of zero, 0 otherwise
0=⍴⍴XA 1 if XA has a rank of zero (scalar), 0 otherwise
0=≡XA 1 if XA has a depth of zero (simple scalar), 0 otherwise
A simple vector comprising as many items as there are rows in
XM1{(↓⍺)⍳↓⍵}
XM2 , where each item is the number of the first row in XM1
XM2
that matches each row in XM2 . See note below.
A nested vector comprising vectors that each correspond to a
position in the original vectors of PV – the first vector contains
↓⍉↑PV
the first item from each vector in PV , padded to be the same
length as the largest vector, and so on ( ⎕ML<2 )
Idiom Description
A nested vector comprising vectors that each correspond to a
position in the original vectors of PV – the first vector contains
↓⍉⊃PV
the first item from each vector in PV , padded to be the same
length as the largest vector, and so on ( ⎕ML≥2 )
A Boolean mask indicating the leading blank spaces in each
^\' '=CA
row of CA
+/^\' '=CA The number of leading blank spaces in each row of CA
+/^\BA The number of leading 1s in each row of BA
{(∨\' '≠⍵)/⍵}
CV without any leading blank spaces
CV
{(+/^\'
CV without any leading blank spaces
'=⍵)↓⍵}CV
XA1⍪←XA2 XA1 redefined to be XA1 with XA2 catenated along its first axis
Idiom Description
0∊⍴XA 1 if XA is empty, 0 otherwise
~0∊⍴XA 1 if XA is not empty, 0 otherwise
⊣⌿XA The first sub-array along the first axis of XA
⊣/XA The first sub-array along the last axis of XA
⊢⌿XA The last sub-array along the first axis of XA
⊢/XA The last sub-array along the last axis of XA
*○NA Euler's idiom (accurate when NA is a multiple of 0J0.5)
0=⊃⍴XA 1 if XA has an empty first dimension, 0 otherwise ( ⎕ML<2 )
1 if XA does not have an empty first dimension, 0 otherwise (
0≠⊃⍴XA
⎕ML<2 )
Notes
NA⊃¨⊂XV is implemented as XV[NA], which is significantly faster. The two are equivalent
but the former now has no performance penalty.
⊃⌽ and ⊃⌽, now take constant time. Without idiom recognition, the time taken depends
linearly on the number of items in the argument.
0=≡ takes a small constant time. Without idiom recognition, the time taken would
depend on the size and depth of the argument, which in the case of a deeply nested
array could be significant.
↓⍉↑ is special-cased only for a vector of nested vectors, each of whose items is of the
same length.
{(↓⍺)⍳↓⍵} can accommodate much larger matrices than its constituent primitives. It is
particularly effective when bound with a left argument using the compose operator:
In this case, the internal hash table for mat is retained so that it does not need to be
generated each time the monadic derived function find is applied to a matrix
argument.
{(∨\' '≠⍵)/⍵} and {(+/^\' '=⍵)↓⍵} are two codings of the same idiom. Both use
the same C code for evaluation.
~∘' '¨↓ typically takes a character matrix argument and returns a vector of character
vectors from which all blanks have been removed. An example might be the character
matrix of names returned by the system function ⎕NL. In general, this idiom
accommodates character arrays of any rank.
{(+/∨\' '≠⌽⍵)↑¨↓⍵} typically takes a character matrix argument and returns a vector
of character vectors. Any embedded blanks in each row are preserved but trailing
blanks are removed. In general, this idiom accommodates character arrays of any rank.
⊃∘⍴¨A (⎕ML<2) and ↑∘⍴¨A (⎕ML>2) avoid having to create an intermediate nested array
of shape vectors.
For an array of vectors, this idiom quickly returns a simple array of the length of each
vector.
For an array of matrices, it returns a simple array of the number of rows in each matrix.
A,←A and A⍪←A optimise the catenation of an array to another array along the last and
first dimension respectively.
Among other examples, this idiom optimises repeated catenation of a scalar or vector
to an existing vector.
props,←⊂ 'Posn' 0 0
props,←⊂'Size' 50 50
vector,←2+4
Note that the idiom is not applied if the value of vector V is shared with another symbol
in the workspace, as illustrated in the following examples:
V1←⍳10
V1,←11
Example 2: the idiom is not used to perform the catenation to V1, because its value is at
that point shared with V2.
V1←⍳10
V2←V1
V1,←11
Example 3: the idiom is not used to perform the catenation to V in Join[1] because its
value is, at that point, shared with the array used to call the function.
∇ V←V Join A
[1] V,←A
∇
(⍳10) Join 11
1 2 3 4 5 6 7 8 9 10 11
⊢⌿XA, ⊢/XA, ⊣⌿XA, and ⊣/XA return the first/last rank (0⌈¯1+⍴⍴A) sub-array along the
first/last axis of XA. For example, if V is a vector, then:
Euler's idiom *○NA produces accurate results for right argument values that are a
multiple of 0J0.5. This is so that Euler's famous identity 0=1+*○0J1 holds, despite pi
being represented as a floating point number.
For clarification; XA↓⍨←NS. If NS is ¯3 then the idiom removes the last -¯3 (that is, 3)
items.
The idiom XM1{(↓⍺)⍳↓⍵}XM2 is still recognised, but since Version 14.0 is no faster than
XM1⍳XM2.
For example, if you have a computer with 4 cores (either real or virtual) and execute an
expression such as (A÷B) where A and/or B contain more than 32,768 elements, then
Dyalog will start 4 separate threads, each performing the division on ¼ of the elements
of the array(s) and simultaneously creating the corresponding ¼ of the result array. The
threads are only started once, and are reused for subsequent multi-threaded
operations.
The maximum number of threads to use can be controlled using 1111⌶, and the parallel
execution threshold is changed using 1112⌶. These "tuning" I-beams should be
considered experimental, and may be changed or replaced in a future release. (See
Dyalog APL Language: Number Of Threads and Dyalog APL Language: Parallel
Execution Threshold).
Note that these scalar dyadic functions are not multi-threaded when applied to arrays
of Boolean or integer values, they are also not multi-threaded for +, - or × when
applied to arrays of 64 bits floating (type 645). Tests show that the overhead of
preparing such arrays for multi-threaded operations outweigh the performance
benefits.
Dyalog APL adopts the J notation introduced in IBM APL2 to represent the value of a
complex number which is written as aJb or ajb (without spaces). The former
representation (with a capital J) is always used to display a value.
Notation
2+¯1*.5
2J1
.3j.5
0.3J0.5
1.2E5J¯4E¯4
120000J¯0.0004
Arithmetic
The arithmetic primitive functions handle complex numbers in the appropriate way.
The absolute value, or magnitude of a complex number is naturally obtained using the
Magnitude function
|3j4
5
+3j4
3J¯4
... which when multiplied by the complex number itself, produces the square of its
magnitude.
3j4×3j¯4
25
Furthermore, adding a complex number and its conjugate produces a real number:
3j4+3j¯4
6
Circular functions
The basic set of circular functions X○Y cater for complex values in Y, while the following
extended functions provide specific features for complex arguments. Note that a and b
are the real and imaginary parts of Y respectively and θ is the phase of Y..
(-X) ○ Y X X ○ Y
-8○Y 8 (-1+Y*2)*0.5
Y 9 a
+Y 10 |Y
Y×0J1 11 b
*Y×0J1 12 θ
Note that 9○Y and 11○Y return the real and imaginary parts of Y respectively:
9 11○3.5J¯1.2
3.5 ¯1.2
Comparison
In comparing two complex numbers X and Y, X=Y is 1 if the magnitude of X-Y does not
exceed ⎕CT times the larger of the magnitudes of X and Y; geometrically, X=Y if the
number smaller in magnitude lies on or within a circle centred on the one with larger
magnitude, having radius ⎕CT times the larger magnitude.
As with real values, complex values sufficiently close to Boolean or integral values are
accepted by functions which require Boolean or integral values. For example:
2j1e¯14 ⍴ 12
12 12
0 ⍱ 1j1e¯15
0
Note that Dyalog APL always stores complex numbers as a pair of 64-bit binary floating-
point numbers, regardless of the setting of ⎕FR. Comparisons between complex
numbers and decimal floating-point numbers will require conversion of the decimal
number to binary to allow the comparison. When ⎕FR=1287, comparisons are always
subject to ⎕DCT, not ⎕CT - regardless of the data type used to represent a number.
This only really comes into play when determining whether the imaginary part of a
complex number is so small that it can be considered to be on the real line. However,
Dyalog recommends that you do not mix the use of complex and decimal numbers in
the same component of an application.
Introduction
The original IEE-754 64-bit binary floating point (FP) data type (also known as type
number 645), that is used internally by Dyalog APL to represent floating-point values,
does not have sufficient precision for certain financial computations – typically
involving large currency amounts. The binary representation also causes errors to
accumulate even when all values involved in a calculation are "exact" (rounded)
decimal numbers, since many decimal numbers cannot be accurately represented
regardless of the precision used to hold them. To reduce this problem, Dyalog APL
includes support for the 128-bit decimal data type described by IEEE-754-2008 as an
alternative representation for floating-point values.
Computations using 128-bit decimal numbers require twice as much space for storage,
and run more than an order of magnitude more slowly on platforms which do not
provide hardware support for the type. At this time, hardware support is only available
from IBM (POWER 6 chips onwards, and recent System z mainframes). Even with
hardware support, a slowdown of a factor of 4 can be expected. For this reason, Dyalog
allows users to decide whether they need the higher-precision decimal representation,
or prefer to stay with the faster and smaller binary representation.
The system variable ⎕FR (for Floating-point Representation) can be set to the value 645
(the installed default) to indicate 64-bit binary FP, or 1287 for 128-bit decimal FP. The
default value of ⎕FR is configurable.
Simply put, the value of ⎕FR decides the type of the result of any floating-point
calculation that APL performs. In other words, when entered into the session:
⎕FR has workspace scope, and may be localised. If so, like most other system variables,
it inherits its initial value from the global environment.
However: Although ⎕FR can vary, the system is not designed to allow "seamless"
modification during the running of an application and the dynamic alteration of ⎕FR is
not recommended. Strange effects may occur. For example, the type of a constant
contained in a line of code (in a function or class), will depend on the value of ⎕FR
when the function is fixed. Similarly, a constant typed into a line in the Session is
evaluated using the value of ⎕FR that pertained before the line is executed. Thus, it
would be possible for the first line of code above to return 0, if it is in the body of a
function. If the function was edited and while suspended and execution is resumed, the
result would become 1. Also note:
⎕FR←1287
x←1÷3
⎕FR←645
x=1÷3
1
The decimal number has 17 more 3s. Using the tolerance which applies to binary floats
(type 645), the numbers are equal. However, the "reverse" experiment yields 0, as
tolerance is much narrower in the 128-bit universe:
⎕FR←645
x←1÷3
⎕FR←1287
x=1÷3
0
Since ⎕FR can vary, it will be possible for a single workspace to contain floating-point
values of both types (existing variables are not converted when ⎕FR is changed). For
example, an array that has just been brought into the workspace from external storage
may have a different type from ⎕FR in the current namespace. Conversion (if necessary)
will only take place when a new floating-point array is generated as the result of "a
calculation". The result of a computation returning a floating-point result will not
depend on the type of the arrays involved in the expression: ⎕FR at the time when a
computation is performed decides the result type, alone.
⎕FR←1287
x←1.1 2.2 3.3
⎕FR←645
⎕DR x
1287
⎕DR 2↑x
1287
128-bit decimal numbers not only have greater precision (roughly 34 decimal digits);
they also have significantly larger range- from ¯1E6145 to 1E6145. Loss of precision is
accepted on conversion from 645 to 1287, but the magnitude of a number may make
the conversion impossible, in which case a DOMAIN ERROR is issued:
⎕FR←1287
x←1E1000
⎕FR←645
x+0
DOMAIN ERROR
WARNING: The use of COMPLEX numbers when ⎕FR is 1287 is not recommended,
because:
• any 128-bit decimal array into which a complex number is inserted or appended
will be forced in its entirety into complex representation, potentially losing
precision
• all comparisons are done using ⎕DCT when ⎕FR is 1287, and this is equivalent to
0 for complex numbers.
Conversion of data from Binary to Decimal is logically equivalent to formatting, and the
reverse conversion is equivalent to evaluating input. These operations are performed
according to the same rules that are used when formatting (and evaluating) numbers
with ⎕PP set to 17 (guaranteeing that the decimal value can be converted back to the
same binary bit pattern). Because the precision of decimal floating-point numbers is
much higher, there will always be a large number of potential decimal values which
map to the same binary number: As with formatting, the rule is that the SHORTEST
decimal number which maps to a particular binary value will be used as its decimal
representation.
Data in component files will be stored without conversion, and only converted when a
computation happens. It should be stored in decimal form if it will repeatedly be used
by application code in which ⎕FR has the value 1287. Even in applications which use
decimal floating point everywhere, reading old component files containing arrays of
type 645, or receiving data via ⎕NA, the .NET interface or other external sources, will
allow binary floating-point values to enter the system and require conversion.
When ⎕FR has the value 1287, the system variable ⎕DCT will be used to specify
comparison tolerance. The default value of ⎕DCT is 1E¯28, and the maximum value is
2.3283064365386962890625E¯10 (the value is chosen to avoid fuzzy comparison of 32-
bit integers).
⎕NA supports the data type "D" to represent the Densely Packed Decimal (DPD) form of
128-bit decimal numbers, as specified by the IEEE-754 2008 standard. Dyalog has
decided to use DPD, which is the format used by IBM for hardware support, on ALL
platforms, although "Binary Integer Decimal" (BID) is the format that Intel libraries use
to implement software libraries to do decimal arithmetic. Experiments have shown that
the performance of 128-bit DPD and BID libraries are very similar on Intel platforms. In
order to avoid the added complication of having two internal representations, Dyalog
has elected to go with the hardware format, which is expected to be adopted by future
hardware implementations.
The support libraries for writing APs and DLLs include new functions to extract the
contents of a value of type D as a string or double-precision binary "float" – and
convert data to D format.
Dyalog APL includes a [Link] class (called Dyalog.Dec128), which will perform
arithmetic on data represented using the "Binary Integer Decimal" format. All
computations performed by the Dyalog.Dec128 class will produce exactly the same
results as if the computation was performed in APL. A "DCT" property allows setting the
comparison tolerance to be used in comparisons, Ceiling/Floor, etc.).
The Dyalog class is modelled closely after the existing [Link] type, providing
the same methods (Add, Ceiling, Compare, CompareTo, Divide, Equals, Finalize, Floor,
FromOACurrency, GetBits, GetHashCode, GetType, GetTypeCode, MemberwiseClone,
Multiply, Negate, Parse, Remainder, Round, Subtract, To*, Truncate, TryParse) and
operators (Addition, Decrement, Division, Equality, Explicit, GreaterThan,
GreaterThanOrEqual, Implicit, Increment, Inequality, LessThan, LessThanOrEqual,
Modulus, Multiply, Subtraction, UnaryNegation, UnaryPlus).
The "bridge" between Dyalog and .NET is able to cast floating-point numbers to or from
[Link], [Link] and Dyalog.Dec128 (and perform all other reasonable
casts to integer types etc.). Casting a Dyalog.Dec128 to or from strings will perform a
"lossless" conversion.
Incoming .NET data types VT_DECIMAL (96-bit integer) and VT_CY (currency value
represented by a 64-bit two's complement integer, scaled by 10,000) are converted to
126-bit decimal numbers (DECFs). This conversion is performed independently of the
value of ⎕FR.
If you want to perform arithmetic on values imported in this way, then you should set
⎕FR to 1287, at least for the duration of the calculations.
Note that the .NET interface converts [Link] to DECFs but does not convert
System.Int64 to DECFs.
1.1.14 Namespaces
Namespaces
They provide the same sort of facility for workspaces as directories do for filesystems.
The analogy, based on DOS, might prove helpful:
Name separator \ .
Parent object .. ##
They provide lexical (as opposed to dynamic) local names. This means that a defined
function can use local variables and functions which persist when it exits and which are
available next time it is called.
• Workspaces can be arranged so that there are many fewer names at each
namespace level. This means that when copying objects from saved workspaces
there is a much reduced chance of a clash with existing names.
• Utility functions in a saved workspace may be coded as a single namespace and
therefore on being copied into the active workspace consume only a single
name. This avoids the complexity and expense of a solution which is sometimes
used in 'flat' workspaces, where such utilities dynamically fix local functions on
each call.
• In flat APL, workspace administration functions such as WSDOC must share names
with their subject namespace. This leads to techniques for trying to avoid name
clashes such as using obscure name prefixes like ⍙⍙L1 This problem is now
virtually eliminated because such a utility can operate exclusively in its own
namespace.
This means that the object need use only a single name in its namespace.
Namespaces
[Link] ← 88
88 [Link] 99
calls dyadic function FOO in namespace UTIL with left and right arguments of 88 and 99
respectively. The interpreter can distinguish between this use of '.' and its use as the
inner product operator, because the leftmost name: UTIL is a (class 9) namespace,
rather than a (class 3) function.
⎕SE is a system namespace which is preserved across workspace load and clear.
Examples
You may also reference a function or operator in a namespace implicitly using the
mechanism provided by ⎕EXPORT (See Dyalog APL Language: Export) and ⎕PATH. If you
reference a name that is undefined in the current space, the system searches for it in
the list of exported names defined for the namespaces specified by ⎕PATH. See Dyalog
APL Language: Path for further details.
Notice that the expression to the right of a dot may be arbitrarily complex and will be
executed within the namespace or ref to the left of the dot.
X.(C←A×B)
X.C
10 12 14
16 18 20
NS1.C
10 12 14
16 18 20
Summary
Apart from its use as a decimal separator (3.14), '.' is interpreted by looking at the
type or class of the expression to its left:
If for example, in the following, the current namespace is #.W, the interpreter evaluates
the line:
A ← [Link] MAT
The rules for name resolution have been generalised for namespaces.
In flat APL, the interpreter searches the state indicator to resolve names referenced by
a defined function or operator. If the name does not appear in the state indicator, then
the workspace-global name is assumed.
For example, if #.FN1 calls XX.FN2 calls #.FN3 calls XX.FN4, then:
• FN1:
◦ is evaluated in #
◦ can see its own dynamic local names
◦ can see global names in #
• FN2:
◦ is evaluated in XX
◦ can see its own dynamic local names
◦ can see global names in XX
• FN3:
◦ is evaluated in #
◦ can see its own dynamic local names
◦ can see dynamic local names in FN1
◦ can see global names in #
• FN4:
◦ is evaluated in XX
◦ can see its own dynamic local names
◦ can see dynamic local names in FN2
◦ can see global names in XX
The following picture illustrates how APL looks down the stack to find names:
│ │ │
│ a+b+c │ │ [8] h references a, b and c
│ │ │ │ │ │
│ ∇h;a │ │ │ │ [7] h localises a
│ │ │ │ │
│ │ │ │ │ [6] g calls X.h
│ │ │ │ │
│ │ │ │ a+b+c │ [5] g references a, b and c
│ │ │ │ │ │ │ │
│ │ │ │ ∇g;a;│ c │ [4] g localises a and c
│ │ │ │ │ │
│ │ │ │ │ │ [3] f calls Y.g
↑ │ │ │ │ │ │
s │ a+b+c │ │ │ [2] f references a, b and c
t │ │ │ │ │ │ │
a │ ∇f;a;b │ │ │ │ [1] f localises a and b
c │ │ │ │ │
k │ a b c │ a b c │ [0] global names a, b and c
└X──────────┴Y──────────┘ in namspaces X and Y
The above diagram represents the SI stack, growing upwards from two namespaces X
and Y, which each have three global names a, b and c.
∇ f;a;b
[1] a+b+c
[2] Y.g
The interpreter looks down the stack and finds local names a and b in f's header
and c in namespace X.
∇ g;a;c
[1] a+b+c
[2] X.h
The interpreter looks down the stack and finds local names a and c in g's header
and b in namespaces Y.
∇ h;a
[1] a+b+c
The interpreter looks down the stack and finds local name a in h's header; b in
f's header; and c in namespace X.
Namespace References
A namespace reference, or ref for short, is a unique data type that is distinct from and
in addition to number and character.
Any expression may result in a ref, but the simplest one is the namespace itself:
X←NS1
X
#.NS1
In this case, the display of X informs you that X refers to the named namespace #.NS1.
∇ FOO ARG
[1] ARG
∇
FOO NS1
#.NS1
⎕NC 'X'
9
You may use a ref to a namespace anywhere that you would use the namespace itself.
For example:
X.A
1
X.B
1 2 3
4 5 6
Notice that refs are references to namespaces, so that if you make a copy, it is the
reference that is copied, not the namespace itself. This is sometimes referred to as a
shallow as opposed to a deep copy. It means that if you change a ref, you actually
change the namespace that it refers to.
X.A+←1
X.A
2
NS1.A
2
∇ FOO nsref
[1] nsref.B+←nsref.A
∇
FOO NS1
NS1.B
3 4 5
6 7 8
FOO X
NS1.B
5 6 7
8 9 10
Notice that the expression to the right of a dot may be arbitrarily complex and will be
executed within the namespace or ref to the left of the dot.
X.(C←A×B)
X.C
10 12 14
16 18 20
NS1.C
10 12 14
16 18 20
Unnamed Namespaces
The monadic form of ⎕NS makes a new (and unique) unnamed namespace and returns
a ref to it.
JOHN←⎕NS ''
JOHN
#.[Namespace]
[Link]←'John'
[Link]
John
[Link]←'Smith'
[Link]←50
Data variables for the second record, PAUL, can be established using strand, or vector,
assignment:
PAUL←⎕NS ''
PAUL.(FirstName LastName Age←'Paul' 'Brown' 44)
The function SHOW can be used to display the data in each record (the function is split
into 2 lines only to fit on the printed page). Notice that its argument is a ref.
∇ R←SHOW PERSON
[1] R←[Link],' ',[Link]
[2] R, ←' is ',⍕[Link]
∇
SHOW JOHN
John Smith is 50
SHOW PAUL
Paul Brown is 44
An alternative version of the function illustrates the use of the :With :EndWith control
structure to execute an expression, or block of expressions, within a namespace:
∇ R←SHOW1 PERSON
[1] :With PERSON
[2] R←FirstName,' ',LastName,' is ',(⍕Age)
[3] :EndWith
∇
SHOW1 JOHN
John Smith is 50
In this case, as only a single expression is involved, it can be expressed more simply
using parentheses.
∇ R←SHOW2 PERSON
[1] R←PERSON.(FirstName,' ',LastName,' is ',(⍕Age))
∇
SHOW2 PAUL
Paul Brown is 44
SHOW3←{
⍵.(FirstName,' ',LastName,' is ',⍕Age)
}
SHOW3 JOHN
John Smith is 50
You can construct arrays of refs using strand notation, array notation, catenate (,),
reshape (⍴) and other structural primitives, as well as certain system functions.
EMP←JOHN PAUL
⍴EMP
2
EMP
#.[Namespace] #.[Namespace]
⎕NC 'EMP'
2
Expressions such as indexing and pick return refs that may in turn be used as follows:
EMP[1].FirstName
John
(2⊃EMP).Age
44
The each (¨) operator may be used to apply a function to an array of refs:
SHOW¨EMP
John Smith is 50 Paul Brown is 44
An array of namespace references (refs) to the left of a '.' is expanded according to the
following rule, where x and y are refs, and exp is an arbitrary expression:
(x y).exp → ([Link])([Link])
If exp evaluates to a function, the items of its argument array(s) are distributed to each
referenced function. In the dyadic case, there is a 3-way distribution among: left
argument, referenced functions and right argument.
Monadic function f:
Dyadic function g:
(x y).a←c d → (x.a←c)(y.a←d)
Note that the array of refs can be of any rank. In the limiting case of a simple scalar
array, the array construct: [Link] is identical to the scalar construct: [Link].
Note that the expression to the right of the '.' pervades a nested array of refs to its left:
Note also that with successive expansions (u v).(x y z). ..., the final number of
"leaf" terms is the product of the number of refs at each level.
Examples
(JOHN PAUL)←()()
[Link]←()()
⍴[Link]
2
[Link][1].FirstName←'Andy'
[Link][1].Age←23
[Link][2].FirstName←'Katherine'
[Link][2].Age←19
[Link]←()()
[Link][1].(FirstName Age←'Tom' 25)
[Link][2].(FirstName Age←'Jamie' 22)
EMP←JOHN PAUL
⍴EMP
2
(⊃EMP).Children.(FirstName Age)
Andy 23 Katherine 19
[Link].(FirstName Age)
Andy 23 Katherine 19 Tom 25 Jamie 22
Distributed Assignment
Assignment pervades nested strands of names to the left of the arrow. The
conformability rules are the same as for scalar (pervasive) dyadic primitive functions
such as '+'. The mechanism can be viewed as a way of naming the parts of a structure.
Examples
EMP.(FirstName Age)
JOHN 43 PAUL 44
EMP.(FirstName Age)
Johnathan 21 Pauline 22
[Link].(FirstName Age)
Andrew 21 Kate 9
More Examples
⍝ Naming of parts:
⍝ Structure rearrangement:
rotate1←{ ⍝ Simple binary tree rotation.
(a b c)d e←⍵
a b(c d e)
}
rotate3←{ ⍝ Compound binary tree rotation.
(a b(c d e))f g←⍵
(a b c)d(e f g)
}
Distributed Functions
[Link]←{↑⍵⍴¨'⎕'}
[Link] ⍳10
⎕
⎕⎕
⎕⎕⎕
⎕⎕⎕⎕
⎕⎕⎕⎕⎕
⎕⎕⎕⎕⎕⎕
⎕⎕⎕⎕⎕⎕⎕
⎕⎕⎕⎕⎕⎕⎕⎕
⎕⎕⎕⎕⎕⎕⎕⎕⎕
⎕⎕⎕⎕⎕⎕⎕⎕⎕⎕
[Link]←{(⍵,¨1)⍴¨'⎕'}
[Link] ⍳10
⎕ ⎕ ⎕ ⎕ ⎕ ⎕ ⎕ ⎕ ⎕ ⎕
⎕ ⎕ ⎕ ⎕ ⎕ ⎕ ⎕ ⎕ ⎕
⎕ ⎕ ⎕ ⎕ ⎕ ⎕ ⎕ ⎕
⎕ ⎕ ⎕ ⎕ ⎕ ⎕ ⎕
⎕ ⎕ ⎕ ⎕ ⎕ ⎕
⎕ ⎕ ⎕ ⎕ ⎕
⎕ ⎕ ⎕ ⎕
⎕ ⎕ ⎕
⎕ ⎕
⎕
Examples
VAR←99 ⍝ #.VAR
)NS X
#.X
[Link]←77 ⍝ [Link]
X.⎕FX'Z←FN R' 'Z←R,VAR'
)NS Y
#.Y
[Link]←88 ⍝ [Link]
Y.⎕FX'Z←(F OP)R' 'Z←F R'
[Link]¨⍳3
1 77 2 77 3 77
[Link] 'VAR:'
VAR: 77
Serialising Namespaces
The Serialisation of an array is its conversion from its internal representation, which
may contain pointers to other structures in the workspace, into a self-contained series
of bytes. This allows the array to be written to a file, transmitted over a socket or used
in a variety of other ways. The de-serialisation of an array is the conversion back to an
internal format whose content and structure is identical to the original array.
If an array contains a reference to a namespace or object that is within the same array,
it can be serialised and de-serialised normally.
The following example uses 220⌶ but applies equally to an array serialised by, for
example ⎕FAPPEND.
Examples
New←0(220⌶)s
New
#.A
New.b
#.B
New.b.c
#.C
)CLEAR
clear ws
'A' ⎕NS ''
'B' ⎕NS ''
'X'⎕NS ''
'X.C'⎕NS ''
A.b←B
B.c←X.C
s←1(220⌶)A
DOMAIN ERROR: Namespace is not self contained
s←1(220⌶)A
∧
Note that a successful 0(220⌶) does not mean that a 1(220⌶) on the result will
succeed. If the original reference was to, say, the MenuBar of ⎕SE you cannot
reconstitute that in #.
Normally, the files associated with external variables remain permanent in that they
survive the APL session or the erasing of the external variable from the workspace.
External variables may be accessed concurrently by several users, or by different nodes
on a network, provided that the appropriate file access controls are established. Multi-
user access to an external variable may be controlled with the system function ⎕FHOLD
between co-operating tasks.
Refer to the sections describing the system functions ⎕XT and ⎕FHOLD in Chapter 6 for
further details.
Examples
V←⍳10
V[2] + 5
7
⎕EX'V'
Information
Support for external variables has been deprecated, and is scheduled for removal
in a future release. For information on how to identify calls to external variables
in your existing codebase, see the Release Notes.
When an Auxiliary Processor is invoked from Dyalog APL, one or more external
functions are fixed in the active workspace. Each external function behaves as if it was a
locked defined function, but is in effect an entry point into the Auxiliary Processor. An
external function occupies only a negligible amount of workspace.
Although Auxiliary Processors are still supported, Dyalog recommends that DLLs/shared
libraries, called via the ⎕NA interface should be used on all platforms in future, and that
existing APs are converted to DLLs/shared libraries.
2 Deprecated Functionality
1. Specify the name of the file into which the JSON5 log messages will be written
(using Dyalog APL Language: Log File For Deprecations with a right argument of
0). By default, the file is created in the current working directory; if you do not
have write permission for the current working directory, you will also need to
include a path. If a filename is not set, then all logging information will be
silently discarded.
2. Specify which deprecated features should be logged (using Dyalog APL
Language: Log Use Of Deprecated Features with a right argument of the names
identifying the deprecated features of interest). If no names are specified then
all logging will be disabled.
The names are release-dependent; for a list of valid names see the v20.0 Release
Notes: Deprecated Functionality.
Each time 13⌶ is called, the new list of features replaces the existing list.
Example
After logging has been enabled, every subsequent use of the specified deprecated
features is logged. Each line in the log file contains a complete JSON5 object, which
includes a description of the deprecated feature and the SI Stack at the point it was
called. The log file can be examined using any text editor or from within a Dyalog
Session. For example:
If an error occurs when writing to the log file, further logging is suspended. The log file
status can be queried at any time by calling 109⌶ with a right argument of 1; the result
is a numeric status code (0 indicates no error) and a character vector describing the
error that was encountered (empty if no error). For example:
(109⌶)1
┌─┬┐
│0││
└─┴┘
or:
(109⌶)1
┌─┬──────────────────────────────────────────┐
│3│The system cannot find the path specified.│
└─┴──────────────────────────────────────────┘
A directory can be scanned for deprecated functionality using Dyalog APL Language:
Scan For Deprecated Files with a right argument of the directory to be scanned. If the
left argument is set to 1, sub-directories will also be scanned. The names of any files
that pertain to deprecated functionality are returned, with labels specifying the reason
for identification.
The labels are release-dependent; for a list of valid labels see the v20.0 Release Notes:
Deprecated Functionality.
Example
1(3535⌶)'.'
./[Link] J0C0
./[Link] OLDWS
./subdir/[Link] J0C0 S32
TradFns may be defined and edited using the Dyalog Editor or may be instantiated from
an array containing source code using the system function ⎕FX. The converse system
functions ⎕CR, ⎕VR, ⎕NR return the original source code.
A defined function or operators is composed of lines. The first line (line 0) is called the
header. Remaining lines are APL statements, called the body.
Only the model is required. If local names and comments are included, they must
appear in the prescribed order.
The tables below show all possible models for defined functions and operators
respectively.
Defined Functions
Note: the right argument Y and/or the result R may be represented by a single name, or
as a blank-delimited list of names surrounded by parentheses. For further details, see
Section 3.1.6.
3.1.3 Statements
A statement is a line of characters understood by APL. It may be composed of:
Each of the four parts is optional, but if present they must occur in the given order
except that successive expressions must be separated by ⋄. Any characters occurring to
the right of the first comment symbol (⍝) that is not within quotes is a comment.
Comments are not executed by APL. Expressions in a line separated by ⋄ are taken in
left-to-right order as they occur in the line. For output display purposes, each separated
expression is treated as a separate statement.
Examples
5×10
50
MULT: 5×10
50
1. the result,
2. the argument(s) and operand(s),
3. additional names in the header line following the model, each name preceded
by a semi-colon character,
4. labels,
5. the argument list of the system function ⎕SHADOW when executed,
6. a name assigned within a dfn.
All names in a defined operation must be valid APL names. The same name may be
repeated in the header line, including the operation name (whence the name is
localised). Normally, the operation name is not a local name.
The same name may not be given to both arguments or operands of a dyadic
operation. The name of a label may be the same as a name in the header line. More
than one label may have the same name. When the operation is executed, local names
in the header line after the model are initially undefined; labels are assigned the values
of line numbers on which they occur, taken in order from the last line to the first; the
result (if any) is initially undefined.
In the case of a defined function, the left argument (if any) takes the value of the array
to the left of the function when called; and the right argument (if any) takes the value
of the array to the right of the function when called. In the case of a defined operator,
the left operand takes the value of the function or array to the left of the operator
when called; and the right operand (if any) takes the value of the function or array to
the right of the operator when called.
During execution, a local name temporarily excludes from use an object of the same
name with an active definition. This is known as LOCALISATION or SHADOWING. A
value or meaning given to a local name will persist only for the duration of execution of
the defined operation (including any time whilst the operation is halted). A name which
is not local to the operation is said to be GLOBAL. A global name could itself be local to
a pendent operation. A global name can be made local to a defined operation during
execution by use of the system function ⎕SHADOW. An object is said to be VISIBLE if
there is a definition associated with its name in the active environment.
Examples
A←1
∇ F
[1] A←10
[2] ∇
∇ F;A
[1] A←10
[2] ∇
Any statement line in the body of a defined operation may begin with a LABEL. A label
is followed by a colon (:). A label is a constant whose value is the number of the line in
the operation defined by system function ⎕FX or on closing definition mode.
The value of a label is available on entering an operation when executed, and it may be
used but not altered in any expression.
Example
⎕VR'PLUS'
∇ R←{A} PLUS B
[1] →DYADIC ⍴⍨2=⎕NC'A' ⋄ R←B ⋄ →END
[2] DYADIC: R←A+B
[3] END:
∇
1 ⎕STOP'PLUS'
2 PLUS 2
PLUS[1]
DYADIC
2
END
3
A Locals Line may appear anywhere between line [0] and the first executable statement
in the function or operator. Locals lines may be interspersed with blank lines and
comments. A Locals Line is identified by starting with a semicolon, prefixed optionally
by whitespace. It may contain a comment at the end.
A Locals Line must be of the form ;name;name;name where name is any valid APL name
or localisable system variable. The names are localised on entry to the function exactly
as if they were specified as locals on line [0].
Example
The function foo shown above localises names a, b, c and d (the indentation on line
[1] in this example is entirely optional)
Syntactical errors on Locals Lines are detected when the user attempts to fix the
function using the Editor or ⎕FX and will causes the operation to fail.
3.1.6 Namelists
The right argument and the result of a function may be specified in the function header
by a single name or by a Namelist. In this context, a Namelist is a blank-delimited list of
names surrounded by a single set of parentheses.
Names specified in a Namelist are automatically local to the function; there is no need
to localise them explicitly using semi-colons.
If the right argument of a function is declared as a Namelist, the function will only
accept a right argument that is a vector whose length is the same as the number of
names in the Namelist. Calling the function with any other argument will result in a
LENGTH ERROR in the calling statement. Otherwise, the elements of the argument are
assigned to the names in the Namelist in the specified order.
Example
Date2IDN 2004 4 30
Year is 2004
Month is 4
Day is 30
Date2IDN 2004 4
LENGTH ERROR
Date2IDN 2004 4
^
Note that if you specify a single name in the Namelist, the function may be called only
with a 1-element vector right argument. If the result of a function is declared as a
Namelist, the values of the names will automatically be stranded together in the
specified order and returned as the result of the function when the function
terminates.
Example
Once locked, and operation may not be displayed or edited and the system functions
⎕CR, ⎕NR and ⎕VR return empty results.
Stop, trace and monitor settings are cancelled when an operation is locked.
A locked operation may not be suspended, nor may a locked operation remain pendent
when execution is suspended. Instead, the state indicator is cut back to the point
where the locked operation was invoked.
• Section [Link]
• Section [Link]
• :Implements
• :Signature
With one exception, these statements are not executable statements and may
theoretically appear anywhere in the body of the function. However, it is
recommended that you place them at the beginning before any executable statements.
The exception is:
The :Access statement is used to specify characteristics for functions that represent
Methods in classes (see Section [Link]). It is also applicable to Classes and Properties.
Element Description
Private| Specifies whether or not the method is accessible from outside
Public the Class or an Instance of the Class. The default is Private .
Instance| Specifies whether the method runs in the Class or Instance. The
Shared default is Instance .
Specifies that the method is exported as a web method. This
WebMethod
applies only to a Class that implements a Web Service.
Applies only to an Instance Method and specifies that the
Overridable Method may be overridden by a Method in a higher Class. See
below.
Applies only to an Instance Method and specifies that the
Override Method overrides the corresponding Overridable Method
defined in the Base Class. See below
Overridable/Override
Normally, a Method defined in a higher Class replaces a Method of the same name that
is defined in its Base Class, but only for calls made from above or within the higher
Class itself (or an Instance of the higher Class). The base method remains available in
the Base Class and is invoked by a reference to it from within the Base Class.
However, a Method declared as being Overridable is replaced in-situ (that is, within
its own Class) by a Method of the same name in a higher Class if that Method is itself
declared with the Override keyword. For further information, see Section [Link].
WebMethod
:Access Public
:Attribute [Link]
The :Attribute statement is used to attach .NET Attributes to a Method (or Class).
Attributes are descriptive tags that provide additional information about programming
elements. Attributes are not used by Dyalog APL but other applications can refer to the
extra information in attributes to determine how these items can be used. Attributes
are saved with the metadata of Dyalog APL .NET assemblies.
Element Description
Name The name of a .NET attribute
ConstructorArgs Optional arguments for the Attribute constructor
Examples
:Attribute ObsoleteAttribute
:Attribute ObsoleteAttribute 'Don''t use' 1
Element Description
Constructor Specifies that the function is a Class Constructor .
Specifies that the Base Constructor be called with the result of the
:Base expr
expression expr as its argument.
Destructor Specifies that the function is a Class Destructor .
Specifies that the function implements the Method MethodName
Method
whose syntax is specified by Interface InterfaceName .
Identifies the function as a Trigger Function which is activated by
changes to variable name1, name2, and so forth. Trigger *
Trigger
specifies a Global Trigger that is activated by the assignment of
any global variable in the same namespace.
This statement identifies the name and signature by which a function is exported as a
method to be called from outside Dyalog APL. Several :Signature statements may be
specified to allow the method to be called with different arguments and/or to specify a
different result type.
Element Description
rslttype Specifies the data type for the result of the method
name Specifies the name of the exported method.
argntype Specifies the data type of the nth parameter
argnname Specifies the name of the nth parameter
Argument and result data types are identified by the names of .NET Types which are
defined in the .NET Assemblies specified by ⎕USING or by a :USING statement.
Examples
In the following examples, it is assumed that the .NET Search Path (defined by :Using
or ⎕USING includes 'System'.
The following statement specifies that the function is exported as a method named
Format which takes a single parameter of type [Link] named Array. The data
type of the result of the method is an array (vector) of type [Link].
The next statement specifies that the function is exported as a method named
Catenate whose result is of type [Link] and which takes 3 parameters. The
first parameter is of type [Link] and is named Dimension. The second is of
type [Link] and is named Arg1. The third is of type [Link] and is
named Arg2.
The next statement specifies that the function is exported as a method named
IndexGen whose result is an array of type System.Int32 and which takes 2
parameters. The first parameter is of type System.Int32 and is named N. The second is
of type System.Int32 and is named Origin.
The next block of statements specifies that the function is exported as a method
named Mix. The method has 4 different signatures; that is, it may be called with 4
different parameter/result combinations.
Introduction
Control Structures are blocks of code in which the execution of APL statements follows
certain rules and conditions. Control structures are implemented using a set of control
words that all start with the colon symbol (:). Control Words are case-insensitive.
There are a number of different types of control structures defined by the control
words, :If, :While, :Repeat, :For (with the supplementary control words :In and
:InEach), :Select, :With, :Trap, :Hold and :Disposable. Each one of these control
words may occur only at the beginning of an APL statement and indicates the start of a
particular type of control structure.
Within a control structure, certain other control words are used as qualifiers. These are
:Else, :ElseIf, :AndIf, :OrIf, :Until, :Case and :CaseList.
A third set of control words is used to identify the end of a particular control structure.
These are
:EndIf, :EndWhile, :EndRepeat, :EndFor, :EndSelect, :EndWith, :EndTrap, :EndHold
and :EndDisposable. Although formally distinct, these control words may all be
abbreviated to :End.
Finally, the :GoTo, :Return, :Leave and :Continue control words may be used to
conditionally alter the flow of execution within a control structure.
Control words, including qualifiers such as :Else and :ElseIf, may occur only at the
beginning of a line or expression in a diamond-separated statement. The only
exceptions are :In and :InEach which must appear on the same line within a :For
expression.
Key to Notation
The following notation is used to describe Control Structures within this section:
|
.-----------------------.
| |
|<--------------. |<--------------.
andor | | | |
code | code |
| | | |
| | | |
:AndIf bexp-----' :OrIf bexp------'
| |
|<----------------------'
|
Notes
Code that precedes a :OrIf control statement, e.g. code placed between a :If
statement and a subsequent :OrIf, will be executed only if the outer condition is false.
If instead the outer condition is true, there is no need to execute the :OrIfstatement ,
so it and any preceding lines of code are skipped.
Code that precedes a :AndIf control statement, e.g. code placed between a :If
statement and a subsequent :AndIf, will only be executed if the outer condition is
true. If instead the outer condition is false, there is no need to execute the :AndIf
statement , so it and any preceding lines of code are skipped.
A potential use for code before a :OrIf or :AndIf is to prepare for the conditional test.
This preparatory work will only be done if required. For example:
Warning
If the test condition (in this case AGE<21) is true, the statements between the :If and
the :EndIf will be executed. If the condition is false, none of these statements will be
run and execution resumes after the :EndIf. Note that the test condition to the right
of :If must return a single element Boolean value 1 (true) or 0 (false).
:If control structures may be considerably more complex. For example, the following
code will execute the statements on lines [2-3] if AGE<21 is 1 (true), or alternatively,
the statement on line [6] if AGE<21 is 0 (false).
Notice that APL executes the expression(s) associated with the first condition that is
true or those following the :Else if none of the conditions are true.
The :AndIf and :OrIf control words may be used to define a block of conditions and
so refine the logic still further. You may qualify an :If or an :ElseIf with one or more
:AndIf statements or with one or more :OrIf statements. You may not however mix
:AndIf and :OrIf in the same conditional block. For example:
Please note that in a :If control structure, the conditions associated with each of the
condition blocks are executed in order until an entire condition block evaluates to true.
At that point, the APL statements following this condition block are executed. None of
the conditions associated with any other condition block are executed. Furthermore, if
an :AndIf condition yields 0 (false), it means that the entire block must evaluate to
false so the system moves immediately on to the next block without executing the
other conditions following the failing :AndIf. Likewise, if an :OrIf condition yields 1
(true), the entire block is at that point deemed to yield true and none of the following
:OrIf conditions in the same block are executed.
[1] I←0
[2] :While I<100
[3] expr1
[4] expr2
[5] I←I+1
[6] :EndWhile
Unless expr1 or expr2 alter the value of I, the above code will execute lines [3-4] 100
times. This loop has a single condition; the value of I. The purpose of the :EndWhile
statement is solely to mark the end of the iteration. It acts the same as if it were a
branch statement, branching back to the :While line.
[1] I←0
[2] :While I<⎕NSIZE ¯1
[3] REC←⎕NREAD ¯1 82 80
[4] I←I+⍴REC
[5] :Until 'Widget'≡6⍴REC
Instead of single conditions, the tests at the beginning and end of the loop may be
defined by more complex ones using :AndIf and :OrIf. For example:
In this example, there are complex conditions at both the start and the end of the
iteration. Each time around the loop, the system tests that both i and j are less than
or equal to 100. If either test fails, the iteration stops. Then, after i and j have been
recalculated by foo, the iteration stops if i+j is equal to or greater than 100, or if either
i or j is negative.
[1] I←0
[2] :Repeat
[3] expr1
[4] expr2
[5] I←I+1
[6] :Until I=100
You can have multiple conditional tests at the end of the loop by adding :AndIf or
:OrIf expressions. The following example will read data from a native file as 80-
character records until it reaches one beginning with the text string 'Widget' or
reaches the end of the file.
[1] :Repeat
[2] REC←⎕NREAD ¯1 82 80
[3] :Until 'Widget'≡6⍴REC
[4] :OrIf 0=⍴REC
[1] :Repeat
[2] REC←⎕NREAD ¯1 82 80
[3] :If 0=⍴REC
[4] :OrIf 'Widget'≡6⍴REC
[5] :Leave
[6] :EndIf
[7] :EndRepeat
The :For loop is used to execute a block of code for a series of values of a particular
control variable. For example, the following would execute lines [2-3] successively for
values of I from 3 to 5 inclusive:
The way a :For loop operates is as follows. On encountering the :For, the expression
to the right of :In is evaluated and the result stored. This is the control array. The
control variable, named to the right of the :For, is then assigned the first value in the
control array, and the code between :For and :EndFor is executed. On encountering
the :EndFor, the control variable is assigned the next value of the control array and
execution of the code is performed again, starting at the first line after the :For. This
process is repeated for each value in the control array.
Note that if the control array is empty, the code in the :For structure is not executed.
Note too that the control array may be any rank and shape, but that its elements are
assigned to the control variable in ravel order.
The control array may contain any type of data. For example, the following code resizes
(and compacts) all your component files
You may also nest :For loops. For example, the following expression finds the
timestamp of the most recently updated component in all your component files.
[1] TS←0
[2] :For FILE :In (↓⎕FLIB '')~¨' '
[3] FILE ⎕FTIE 1
[4] START END←2⍴⎕FSIZE 1
[5] :For COMP :In (START-1)↓⍳END-1
[6] TS⌈←¯1↑⎕FREAD FILE COMP
[7] :EndFor
[8] ⎕FUNTIE 1
[9] :EndFor
The :For control structure can also take multiple variables. This has the effect of doing
a strand assignment each time around the loop.
For example :For a b c :in (1 2 3)(4 5 6), sets a b c←1 2 3, first time around
the loop and a b c←4 5 6, the second time.
Another example is :For i j :In ⍳⍴Matrix, which sets i and j to each row and
column index of Matrix.
For a single control variable, the effect of the keywords is identical but for multiple
control variables the values vector is inverted.
1 2 3
3 4 5
5 6 7
7 8 9
Notice that in the second case, the number of items in the values vector is the same as
the number of control variables. A more typical example might be.
Here, each time around the loop, control variable a is set to the next item of avec, b to
the next item of bvec and c to the next item of cvec.
[1] :Select I
[2] :Case 1
[3] 'I is 1'
[4] :Case 2
[5] 'I is 2'
[6] :Else
[7] 'I is neither 1 nor 2'
[8] :EndSelect
In this case, the system compares the value of the array expression to the right of the
:Select statement with each of the expressions to the right of the :Case statements
and executes the block of code following the one that matches. If none match, it
executes the code following the :Else (which is optional). Note that comparisons are
performed using the ≡ primitive function, so the arrays must match exactly. Note also
that not all of the :Case expressions are necessarily evaluated because the process
stops as soon as a matching expression is found.
Instead of a :Case statement, you may also use a :CaseList statement. If so, the
enclose of the array expression to the right of :Select is tested for membership of the
array expression to the right of the :CaseList using the ∊ primitive function.
Note also that any code placed between the :Select and the first :Case or :CaseList
statements are unreachable; future versions of Dyalog APL may generate an error when
attempting to fix functions which include such code.
Example
[1] :Select ?6 6
[2] :Case 6 6
[3] 'Box Cars'
[4] :Case 1 1
[5] 'Snake Eyes'
[6] :CaseList 2⍴¨⍳6
[7] 'Pair'
[8] :CaseList (⍳6),¨⌽⍳6
[9] 'Seven'
[10] :Else
[11] 'Unlucky'
[12] :EndSelect
:With F.G
Values←4 3⍴0
RowTitles←'North' 'South' 'East' 'West'
ColTitles←'Cakes' 'Buns' 'Biscuits'
:EndWith
• Exiting the defined function from within a :With control structure causes the
space to revert to the one from which the function was called.
On leaving the :With control structure, execution reverts to the original namespace.
Notice however that the interpreter does not detect branches (→) out of the control
structure. :With control structures can be nested in the normal fashion:
:Hold provides a mechanism to control thread entry into a critical section of code.
tkns must be a simple character vector or scalar, or a vector of character vectors. tkns
represents a set of "tokens", all of which must be acquired before the thread can
continue into the control structure. :Hold is analogous to the component file system
⎕FHOLD which is used to synchronise access between processes. See also Earlier
Release Notes: Fhold.
Within the whole active workspace, a token with a particular value may be held only
once. If the hold succeeds, the current thread acquires the tokens and execution
continues with the first phrase in the control structure. On exit from the structure, the
tokens are released for use by other threads. If the hold fails, because one or more of
the tokens is already in use:
'a'
'Red'
'#.Util'
''
'Program Files'
… or a number of tokens:
Pre-processing removes trailing blanks from each token before comparison, so that, for
example, the following two statements are equivalent:
Unlike ⎕FHOLD, a thread does not release all existing tokens before attempting to
acquire new ones. This enables the nesting of holds, which can be useful when multiple
threads are concurrently updating parts of a complex data structure.
However, with the nesting of holds comes the possibility of a "deadlock". For example,
consider the two threads:
Thread 1 Thread 2
In this case if both threads succeed in acquiring their first hold, they will both block
waiting for the other to release its token.
1. If there is an :Else clause in the control structure, execution jumps to the :Else
clause.
2. Otherwise, APL issues an error (1008) DEADLOCK.
You can avoid deadlock by ensuring that threads always attempt to acquire tokens in
the same chronological order, and that threads never attempt to acquire tokens that
they already own.
Note that token acquisition for any particular :Hold is atomic, that is, either all of the
tokens or none of them are acquired. The following example cannot deadlock:
Thread 1 Thread 2
:Hold 'red'
...
:Hold 'green' 'red'
:Hold 'green'
...
...
:EndHold
:EndHold
:EndHold
Examples
:Hold could be used for example, during the update of a complex data structure that
might take several lines of code. In this case, an appropriate value for the token would
be the name of the data structure variable itself, although this is just a programming
convention: the interpreter does not associate the token value with the data variable.
:Hold'Struct'
... ⍝ Update Struct
Struct ← ...
:EndHold
The following example shows code that holds two positions in a vector while the
contents are exchanged.
:Hold ⍕¨to fm
:If >/vec[fm to]
vec[fm to]←vec[to fm]
:End
:End
Between obtaining the next available file tie number and using it:
:Hold '⎕FNUMS'
tie←1+⌈/0,⎕FNUMS
fname ⎕FSTIE tie
:End
The above hold is not necessary if the code is combined into a single line:
or,
tie←fname ⎕FSTIE 0
Note that :Hold, like its component file system counterpart ⎕FHOLD, is a device to
enable co-operating threads to synchronise their operation.
:Hold does not prevent threads from updating the same data structures concurrently,
it prevents threads only from :Holding the same tokens.
High-Priority Callbacks
:Hold with a non-zero number of tokens is not permitted in a high-priority callback and
an attempt to use it will cause the error:
ecode is an integer scalar or vector containing the list of event codes which are to be
handled during execution of the segment of code between the :Trap and :End[Trap]
statements. Note that event codes 0 and 1000 are wild cards that means any event
code in a given range. See Section 8.1.2.
Operation
• If the error occurred within a sub-function, the system cuts the state indicator
back to the function containing the :Trap keyword. In this respect, :Trap
behaves like ⎕TRAP with a 'C' qualifier.
• If the :Trap segment contains a :Case[List] ecode statement whose ecode
matches the event code of the error that has occurred, execution continues from
the statement following that :Case[List] ecode.
• Otherwise, if the :Trap segment contains a :Else statement, execution
continues from the first statement following the :Else statement.
Note that the error trapping is in effect only during execution of the initial code
segment. When a trapped error occurs, further error trapping is immediately disabled
(or surrendered to outer level :Traps or ⎕TRAPs). In particular, the error trap is no
longer in effect during processing of :Case[List]'s argument or in the code following
the :Case[List] or :Else statement. This avoids the situation sometimes
encountered with ⎕TRAP where an infinite "trap loop" occurs.
Examples
∇ lx
[1] :Trap 1000 ⍝ Cutback and exit on interrupt
[2] Main ...
[3] :EndTrap
∇
The following are equivalent. See Dyalog APL Language: Branch for further details.
→Exit
:GoTo Exit
→(N<I←I+1)/End
:GoTo (N<I←I+1)/End
→1+⎕LC
:GoTo 1+⎕LC
→10
:GoTo 10
When executed within a :For loop, the effect is to start the body of the loop with the
next value of the iteration variable.
When executed within a :Repeat or :While loop, if there is a trailing test that test is
executed and, if the result is true, the loop is terminated. Otherwise the leading test is
executed in the normal fashion.
Typically, .NET classes implement a special interface called IDisposable which provides a
standard way for applications to release memory and other resources when an instance
is removed. Furthermore, the C# language has the using keyword, which "Provides a
convenient syntax that ensures the correct use of IDisposable objects."
The :Disposable array statement in Dyalog APL provides a similar facility to C#'s
using. array may be a scalar or vector of namespace references.
When the block is exited, any .NET objects in array that implement IDisposable will
have [Link] called on them.
Note that exit includes normal exit as the code drops through :EndDisposable, or if an
error occurs and is trapped, or if branch (→) is used to exit the block, or anything else.
In the above example, when the :EndDisposable statement is reached, the system
disposes of the Font object f (and all the resources associated with it) by calling
(IDisposable)[Link](). A subsequent reference to f would generate VALUE
ERROR.
In the above example, Dispose() is called on each of the Font objects in fonts during
the processing of :EndDisposable.
In this example, Dispose() is called on the Font objects in fonts during the processing
of the branch statement →0.
Example (TrapExit)
:trap 0
:else
⎕←'failed'
:endif
Here, the objects are disposed of when the DOMAIN ERROR generated by the expression
÷0 causes the stack to be cut back to the :Else clause. At this point (just before the
execution of the :Else clause) the name class of fonts becomes 0.
Using the APL Line Editor, functions and operators are defined by entering Definition
Mode. This mode is opened and closed by the Del symbol, ∇. Within this mode, all
evaluation of input is deferred. The standard APL line editor (described below) is used
to create and edit operations within definition mode.
Operations may also be defined using the system function ⎕FX (implicit in a ⎕ED fix)
which acts upon the canonical (character), vector, nested or object representation form
of an operation. (See Dyalog APL Language: Fx for details.)
The line editor recognises three forms for the opening request.
The opening ∇ symbol is followed by the header line of a defined operation. Redundant
blanks in the request are permitted except within names. If acceptable, the editor
prompts for the first statement of the operation body with the line-number 1 enclosed
in brackets. On successful completion of editing, the defined operation becomes the
active definition in the workspace.
Example
∇R←FOO
[1] R←10
[2] ∇
FOO
10
The given operation name must not have an active referent in the workspace,
otherwise the system reports defn error and the system editor is not invoked:
)VARS
SALES X Y
∇R←SALES Y
defn error
The header line of the operation must be syntactically correct, otherwise the system
reports defn error and the system editor is not invoked:
∇R←A B C D:G
defn error
The ∇ symbol followed by the name of a defined operation and then by a closing ∇,
causes the display of the named operation. Omitting the function name causes the
suspended operation (that is, the one at the top of the state indicator) to be displayed
and opened for editing.
Example
∇FOO∇
∇ R←FOO
[1] R←10
∇
)SI
#.FOO[1] *
∇
∇ R←FOO
[1] R←10
[2]
The ∇ symbol on its own causes the suspended operation (that is, the one at the top of
the state indicator) to be displayed. The editor then prompts for a statement or editing
directive with a line-number one greater than the highest line-number in the function.
If the state indicator is empty, the system reports defn error and definition mode is
not entered.
The ∇ symbol followed by the name of an active defined operation causes the display of
the named operation. The editor then prompts for input as described above. If the
name given is not the name of an active referent in the workspace, the opening
request is taken to be the creation of a new operation as described in paragraph 1. If
the name refers to a pendent operation, the editor issues the message warning
pendent operation prior to displaying the operation. If the name refers to a locked
operation, the system reports defn error and definition mode is not entered.
The ∇ symbol followed by the name of an active defined operation and an editing
directive causes the operation to be opened for editing and the editing directive
actioned. If the editing directive is invalid, it is ignored by the editor which then
prompts with a line-number one greater than the highest line-number in the
operation. If the name refers to a pendent operation, the editor issues the message
warning pendent operation prior to actioning the editing directive. If the name
refers to a locked operation, the system reports defn error and definition mode is not
entered.
Example
∇FOO[2]
[2] R←R*2
[3] ∇
Editing Directives
Editing directives, summarised in Figure 2(iv) are permitted as the first non-blank
characters either after the operation name on opening definition mode for an active
defined function, or after a line-number prompt.
Syntax Description
∇ Closes definition mode
[⎕] Displays the entire operation
[⎕n] Displays the operation starting at line n
[n⎕] Displays only line n
[∆n] Deletes line n
[n∆m] Deletes m lines starting at line n
[n] Prompts for input at line n
[n]s Replaces or inserts a statement at line n
Edits line n placing the cursor at character position m where an Edit
[n⎕m]
Control Symbol performs a specific action.
Line Numbers
Line numbers are associated with lines in the operation. Initially, numbers are assigned
as consecutive integers, beginning with [0] for the header line. The number associated
with an operation line remains the same for the duration of the definition mode unless
altered by editing directives. Additional lines may be inserted by decimal numbering.
Up to three places of decimal are permitted. On closing definition mode, operation
lines are re-numbered as consecutive integers.
The editor always prompts with a line number. The response may be a statement line
or an editing directive. A statement line replaces the existing line (if there is one) or
becomes an additional line in the operation:
∇R←A PLUS B
[1] R←A+B
[2]
Position
The editing directive [n], where n is a line number, causes the editor to prompt for
input at that line number. A statement or another editing directive may be entered. If
a statement is entered, the next line number to be prompted is the previous number
incremented by a unit of the display form of the last decimal digit. Trailing zeros are
not displayed in the fractional part of a line number:
[2] [0.8]
[0.8] ⍝ MONADIC OR DYADIC +
[0.9] ⍝ A ←→ OPTIONAL ARGUMENT
[1]
The editing directive [n]s, where n is a line number and s is a statement, causes the
statement to replace the current contents of line n, or to insert line n if there is none:
Delete
The editing directive [∆n], where n is a line number, causes the statement line to be
deleted. The form [n∆m], where n is a line number and m is a positive integer, causes m
consecutive statement lines starting from line number n to be deleted.
Edit
The editing directive [n⎕m], where n is a line number and m is an integer number,
causes line number n to be displayed and the cursor placed beneath the m character on
a new line for editing. The response is taken to be edit control symbols selected from:
Invalid edit symbols are ignored. If there are no valid edit symbols entered, or if there
are only deletion or space insertion symbols, the statement line is re-displayed with
characters deleted and spaces inserted as specified. The cursor is placed at the first
inserted space position or at the end of the line if none. Characters may be added to
the line which is then interpreted as seen.
Examples
[1] [1⎕7]
[1] R←A+B
,→(0=⎕NC'A')⍴1←⎕LC ⋄
[1] →(0=⎕NC'A')⍴1←⎕LC ⋄ R←A+B
.⋄→END
[2] R←B
[3] END:
[4]
The form [n⎕0] causes the line number n to be displayed and the cursor to be
positioned at the end of the displayed line, omitting the edit phase.
Display
The editing directive [⎕] causes the entire operation to be displayed. The form [⎕n]
causes all lines from line number n to be displayed. The form [n⎕] causes only line
number n to be displayed:
[4] [0⎕]
[0] R←{A} PLUS B
[0]
[0] [⎕]
[0] R←{A} PLUS B
[0.1] ⍝ MONADIC OR DYADIC +
[1] →(0=⎕NC'A')⍴1+⎕LC ⋄ R←A+B ⋄→END
[2] R←B
[3] 'END:
[4]
The editing directive ∇ causes definition mode to be closed. The new definition of the
operation becomes the active version in the workspace. If the name in the operation
header (which may or may not be the name used to enter definition mode) refers to a
pendent operation, the editor issues the message warning pendent operation before
exiting. The new definition becomes the active version, but the original one will
continue to be referenced until the operation completes or is cleared from the state
indicator.
If the name in the operation header is the name of a visible variable or label, the editor
reports defn error and remains in definition mode. It is then necessary to edit the
header line or quit.
If the header line is changed such that it is syntactically incorrect, the system reports
defn error, and re-displays the line leaving the cursor beyond the end of the text on
the line. Backspace/linefeed editing may be used to alter or cancel the change:
Local names may be repeated. However, the line editor reports warning messages as
follows:
1. If a name is repeated in the header line, the system reports "warning duplicate
name" immediately.
2. If a label has the same name as a name in the header line, the system reports
"warning label name present in line 0" on closing definition mode.
3. If a label has the same name as another label, the system reports "warning
duplicate label" on closing definition mode.
4. If a name is repeated in the header line, the system reports "warning duplicate
name" immediately.
5. If a label has the same name as a name in the header line, the system reports
"warning label name present in line 0" on closing definition mode.
6. If a label has the same name as another label, the system reports "warning
duplicate label" on closing definition mode.
Improper syntax in expressions within statement lines of the function is not detected
by the system editor with the following exceptions:
• If the number of opening parentheses in each entire expression does not equal
the number of closing parentheses, the system reports "warning unmatched
parentheses", but accepts the line.
• If the number of opening brackets in each entire expression does not equal the
number of closing brackets, the system reports "warning unmatched brackets",
but accepts the line.
These errors are not detected if they occur in a comment or within quotes. Other
syntactical errors in statement lines will remain undetected until the operation is
executed.
Example
[4] R←(A[;1)=2)⌿⍎EXP,'×2
warning unmatched parentheses
warning unmatched brackets
[5]
Note that there is an imbalance in the number of quotes. This will result in a SYNTAX
ERROR when this operation is executed.
The user may quit definition mode by typing the INTERRUPT character. The active
version of the operation (if any) remains unchanged.
In its simplest form, a dfn is an APL expression enclosed in curly braces {}, possibly
including the special characters ⍺ and ⍵ to represent the left and right arguments of the
function respectively. For example:
mean←{(+/⍵)÷⍴⍵}
mean¨(2 3)(4 5)
2.5 4.5
dfns can be defined and used in any context where an APL function may be found, in
particular:
2 The terms dfn and dop refer to a special type of function (or operator) unique to Dyalog.
They were originally named dynamic functions and dynamic operators, later abbreviated to
Dfns and Dops or D-Fns and D-Ops, but all these terms have been dropped in favour of the
current ones.
For example in the following, the expressions sum← and num← create local definitions
sum and num.
An assignment to ⍵ is not allowed and will result in an error. For assignment to ⍺, see
Section 3.2.3.
When the interpreter encounters a local definition, a new local name is created. The
name is shadowed dynamically exactly as if the assignment had been preceded by:
⎕SHADOW name ⋄.
It is important to note the distinction between the two types of statement above.
There can be many assignment statements, each introducing a new local definition, but
only a single expression where the result is not assigned. As soon as the interpreter
encounters such an expression, it is evaluated and the result returned immediately as
the result of the function.
... as soon as the interpreter encounters the expression sum,num, the function
terminates with the two item result (sum,num) and the following line is not evaluated.
To display arrays to the session from within a dfn, you can use the explicit display forms
⎕← or ⍞← as in:
Note that local definitions can be used to specify local nested dfns:
The expression to the right of ⍺← is evaluated only if its dfn is called with no left
argument.
Note that the syntax must be exactly ⍺←, that is, it cannot contain parentheses, and so
on.
Ambivalence
foo←{
⍺←⊢
⍺ goo ⍵
}
If foo is given a left argument, this is passed to goo. Otherwise, ⍺ is assigned ⊢ and the
last line is ⊢ goo ⍵, which is a monadic call on goo followed by the ⊢ (Right) of the
result of goo, which is the same value.
over←{
⍺←⍣0
(⍺⍺ ⍺)⍵⍵(⍺⍺ ⍵)
}
If the function derived from over is given a left argument, this argument is
preprocessed by the left operand ⍺⍺ and the result is passed to the right operand ⍵⍵.
Otherwise, ⍺ is assigned ⍣0 and the last line is (⍺⍺⍣0)⍵⍵(⍺⍺ ⍵), which is a monadic call
on ⍵⍵ followed by not applying ⍺⍺ to the result of ⍵⍵, returning it unmodified.
The assignment ⍺←⍵ allows a function to act as if the commute operator (⍨) was applied
to it twice:
sort←{
⍺←⍵
⍵[⍋⍺]
}
If sort is given a left argument, the right argument is sorted according to the left
argument. Otherwise, ⍺ is assigned ⍵ and the last line is ⍵[⍋⍵], which has the right
argument sorted according to itself. This is, therefore, equivalent to sort←{⍵[⍋⍺]}⍨⍨ or
sort←{⍺[⍋⍵]}⍨.
3.2.4 Guards
A Guard is a Boolean-single valued expression followed on the right by a ':'. For
example:
The guard is followed by a single APL expression: the result of the function.
A dfn may contain any number of guarded expressions each on a separate line (or
collected on the same line separated by diamonds). Guards are evaluated in turn until
one of them yields a 1. The corresponding expression to the right of the guard is then
evaluated as the result of the function.
sign←{
⍵>0: '+ve' ⍝ Positive
⍵=0: 'zero' ⍝ zero
'-ve' ⍝ Negative (Default)
}
Note again that any code following the first unguarded expression (which terminates
the function) could never be executed and would therefore be redundant.
For example, in the following function, variable type is defined both within which itself
and within the inner function fn1. When fn1 calls outward to fn2 and fn2 refers to
type, it finds the outer one (with value 'lexical') rather than the one defined in fn1:
which←{
type←'lexical'
fn1←{
type←'dynamic'
fn2 ⍵
}
fn2←{
type ⍵
}
fn1 ⍵
}
which'scope'
lexical scope
3.2.7 Error-Guards
An error-guard is (an expression that evaluates to) a vector of error numbers (see
Section 8.1.2), followed by the digraph: ::, followed by an expression, the body of the
guard, to be evaluated as the result of the function. For example:
In common with :Trap and ⎕TRAP, error numbers 0 and 1000 are catch-alls for
synchronous errors and interrupts respectively.
When an error is generated, the system searches dynamically through the calling
functions for an error-guard that matches the error. If one is found, the execution
environment is unwound to its state immediately prior to the error-guard's execution
and the body of the error-guard is evaluated as the result of the function. This means
that, during evaluation of the body, the guard is no longer in effect and so the danger
of a hang caused by an infinite "trap loop", is avoided.
Notice that you can provide "cascading" error trapping in the following way:
0::try_2nd
0::try_1st
expr
In this case, if expr generates an error, its immediately preceding: 0:: catches it and
evaluates try_1st leaving the remaining error-guard in scope. If try_1st fails, the
environment is unwound once again and try_2nd is evaluated, this time with no error-
guards in scope.
Examples
Open returns a handle for a component file. If the exclusive tie fails, it attempts a share-
tie and if this fails, it creates a new file. Finally, if all else fails, a handle of 0 is returned.
6 4 2 div 3 2
→ 6 4 2 div 3 2 0
→ 6 4 2 div 3 2 1
→ 2 2 2
The final example shows the unwinding of the local environment before the error-
guard's body is evaluated. Local name trap is set to describe the domain of its
following error-guard. When an error occurs, the environment is unwound to expose
trap's statically correct value.
add←{
trap←'domain' ⋄ 11::trap
trap←'length' ⋄ 5::trap
⍺+⍵
}
Note
Following the setting of an error-guard, subsequent function calls will disable tail call
optimisation:
{
22::'Oops!' ⍝ this error-guard means that ...
tie←⍵ ⎕FTIE 0
subfn tie ⍝ ... tail call not optimised
}
One way to maintain the tail call optimisation in the presence of an error-guard is to
isolate it within an inner function:
{
tie←{
22::0 ⍝ error-guard local to inner fn
⍵ ⎕FTIE 0
}⍵
tie=0:'Oops!'
subfn tie ⍝ ... so this is a tail call
}
3.2.8 Dops
The operator equivalent of a dfn is distinguished by the presence of either of the
compound symbols ⍺⍺ or ⍵⍵ anywhere in its definition.
where ⍺⍺ and ⍵⍵ are the left and right operands (functions or arrays) respectively, and ⍺
and ⍵ are the arguments of the derived function.
Example
The following monadic each operator applies its function operand only to unique
elements of its argument. It then distributes the result to match the original argument.
This can deliver a performance improvement over the primitive each (¨) operator if the
operand function is costly and the argument contains a significant number of duplicate
elements. Note however, that if the operand function causes side effects, the operation
of dop and primitive versions will be different.
The dyadic else operator applies its left (else right) operand to its right argument
depending on its left argument.
else←{
⍺: ⍺⍺ ⍵ ⍝ True: apply Left operand
⍵⍵ ⍵ ⍝ Else, .. Right ..
}
0 1 ⌈else⌊¨ 2.5 ⍝ Try both false and true.
2 3
3.2.9 Recursion
A recursive dfn can refer to itself using its name explicitly, but because we allow
unnamed functions, we also need a special symbol for implicit self-reference: '∇'. For
example:
fact←{ ⍝ Factorial ⍵.
⍵≤1: 1 ⍝ Small ⍵, finished,
⍵×∇ ⍵-1 ⍝ Otherwise recur.
}
Implicit self-reference using '∇' has the further advantage that it incurs less
interpretative overhead and is therefore quicker. Tail calls using '∇' are particularly
efficient.
Recursive dops refer to their derived functions, that is the operator bound with its
operand(s) using ∇ or the operator itself using the compound symbol: ∇∇. The first form
of self reference is by far the more frequently used.
Example
The following example shows a rather contrived use of the second form of (operator)
self reference. The exp operator composes its function operand with itself on each
recursive call. This gives the effect of an exponential application of the original operand
function:
Example
The following sequence shows an example of combining dfns and dops in an attempt to
find Pythagorean triples: (3 4 5)(5 12 13) ...
sqrt 9 16 25
3 4 5
pairs 3
1 1 1 2 1 3 2 1 2 2 2 3 3 1 3 2 3 3
less←{</⊃⍵}
less(3 4)(4 3)
1 0
A Larger Example
Function tokens uses nested local dfns to split an APL expression into its constituent
tokens. Note that all calls on the inner functions: lex, acc, and the unnamed dfn in
each token case, are tail calls. In fact, the only stack calls are those on function: all,
and the unnamed function: {⍵∨¯1⌽⍵}, within the "Char literal" case.
hd∊alph:⍺{ ⍝ Name
size←⍵ all alph,⎕D
⍺ acc size ⍵
}⍵
When a dfn calls a sub-function, the result of the call may or may not be modified by
the calling function before being returned. A call where the result is passed back
immediately without modification is termed a tail call.
For example in the following, the first call on function fact is a tail call because the
result of fact is the result of the whole expression, whereas the second call isn't
because the result is subsequently multiplied by ⍵.
Tail calls occur frequently in dfns, and the interpreter optimises them by re-using the
current stack frame instead of creating a new one. This gives a significant saving in both
time and workspace usage. It is easy to check whether a call is a tail call by tracing it. An
embedded call will pop up a new trace window for the called function, whereas a tail
call will re-use the current one.
Using tail calls can improve code performance considerably, although at first the
technique might appear obscure. A simple way to think of a tail call is as a branch with
arguments. The tail call, in effect, branches to the first line of the function after
installing new values for ⍵ and ⍺.
In general, when coding a loop, we use the following steps; possibly in a different order
depending on whether we want to test at the "top" or the "bottom" of the loop.
loop←{⍝ init
⎕CT>⍺-⍵:⍵ ⍝ test
⍺ ∇ Next ⍵ ⍝ proc, mod, jump
}
3.2.11 Restrictions
• Dfns need not return a result. However even a non-result-returning expression
will terminate the function, so you can't, for example, call a non-result-returning
function from the middle of a dfn.
• You can trace a dfn only if it is defined on more than one line. Otherwise it is
executed atomically in the same way as an execute (⍎) expression. This
deliberate restriction is intended to avoid the confusion caused by tracing a line
and seeing nothing change on the screen.
• dfns do not currently support ⎕CS which, if used, generates a NONCE ERROR.
• ⎕SHADOW ignores dfns when looking down the stack for a traditional function
(tradfn) in which to make a new local name.
• dfns do not support control structures or branch.
• dfns do not support modified assignment such as X plus←10 where X is an array
and plus is a function. In this example, both X and plus would be assigned the
value 10.
• ⎕MONITOR does not apply to dfns and dops.
Supplied Workspaces
You can find many samples of dfns and dops in utility workspace [Link] in the ws
sub-directory.
A Class may optionally derive from another Class, which is referred to as its Base Class.
A Class may contain Methods, Properties and Fields (commonly referred to together as
Members) which are defined within the body of the class script or are inherited from
other Classes. This version of Dyalog APL does not support Events although it is
intended that these will be supported in a future release. However, Classes that are
derived from .NET types may generate events using 4 ⎕NQ.
A Class that is defined to derive from another Class automatically acquires the set of
Properties, Methods and Fields that are defined by its Base Class. This mechanism is
described as inheritance.
A Class may extend the functionality of its Base Class by adding new Properties,
Methods and Fields or by substituting those in the Base Class by providing new versions
with the same names as those in the Base Class.
Although Classes are generally used as blueprints for the creation of instances, a class
can have Shared members which can be used without first creating an instance.
Class Names
Class names must adhere to the general rules for naming APL objects, and in addition
should not conflict with the name of a .NET Type that is defined in any of the .NET
Namespaces on the search path specified by ⎕USING.
A class script begins with a Section 4.9.4 and ends with a :EndClass statement.
)CLEAR
clear ws
)ED ○Animal
[an edit window opens containing the following skeleton Class script ...]
:Class Animal
:EndClass
)CLASSES
Animal
⎕NC⊂'Animal'
9.4
Note that the contents of the Class Script defines the Class in its entirety. You may not
add or alter functions by editing them independently and you may not add variables by
assignment or remove objects with ⎕EX.
When you re-fix a Class Script using the Editor or with ⎕FIX, the original Class is
discarded and the new definition, as specified by the Script, replaces the old one in its
entirety.
Note
For example, if X is not a public member of the class MyClass, then the following
expression will insert a variable X into the namespace which surrounds the class:
MyClass.X←99
The namespace is analogous to the namespace associated with a GUI object and will be
re-initialised (emptied) whenever the Class is re-fixed. Objects in this parallel
namespace are not visible from inside the Class or an Instance of the Class.
4.1.4 Inheritance
If you want a Class to derive from another Class, you simply add the name of that Class
to the :Class statement using colon+space as a separator.
If a Class has a Base Class, it automatically acquires all of the Public Properties,
Methods and Fields defined for its Base Class unless it replaces them with its own
members of the same name. This principle of inheritance applies throughout the Class
hierarchy. Note that Private members are not subject to inheritance.
Warning
When a class is fixed, it keeps a reference (a pointer) to its base class. If the global
name of the base class is expunged, the derived class will still have the base class
reference, and the base class will therefore be kept alive in the workspace. The
derived class will be fully functional, but attempts to edit it will fail when it
attempts to locate the base class as the new definition is fixed.
At this point, if a new class with the original base class name is created, the derived
class has no way of detecting this, and it will continue to use the old and invisible
version of the base class. Only when the derived class is re-fixed, will the new base
class be detected.
If you edit, re-fix or copy an existing base class, APL will take care to patch up the
references, but if the base class is expunged first and recreated later, APL is unable to
detect the substitution. You can recover from this situation by editing or re-fixing the
derived class(es) after the base class has been substituted.
Copying Classes
See Dyalog APL Language: Copy and Dyalog APL Language: Copy.
You may define a Class that derives from any of the .NET Types by specifying the name
of the .NET Type and including a :USING statement that provides a path to the .NET
Assembly in which the .NET Type is located.
Example
You may define a Class that derives from any of the Dyalog APL GUI objects by
specifying the name of the Dyalog APL GUI Class in quotes.
For example, to define a Class named Duck that derives from a Poly object, the Class
specification would be:
:Class Duck:'Poly'
:EndClass
The Base Constructor for such a Class is the ⎕WC system function.
4.1.5 Instances
A Class is generally used as a blueprint or model from which one or more Instances of
the Class are constructed. Note however that a class can have Shared members which
can be used directly without first creating an instance.
You create an instance of a Class using the ⎕NEW system function which is monadic.
The 1-or 2-item argument to ⎕NEW contains a reference to the Class and, optionally,
arguments for its Constructor function.
When ⎕NEW executes, it creates a regular APL namespace to contain the Instance, and
within that it creates an Instance space, which is populated with any Instance Fields
defined by the class (with default values if specified), and pointers to the Instance
Method and Property definitions specified by the Class.
The Constructor function is typically used to initialise the instance and may establish
variables in the instance namespace.
4.2 Constructors
4.2.1 Constructors
A Constructor is a special function defined in the Class script that is to be run when an
Instance of the Class is created by ⎕NEW. Typically, the job of a Constructor is to initialise
the new Instance in some way.
Note that it is also essential to define the Constructor to be Public, with a :Access
Public statement, because like all Class members, Constructors default to being
Private. Private Constructors currently have no use or purpose, but it is intended that
they will be supported in a future release of Dyalog APL.
A Constructor function may be niladic or monadic and must not return a result.
A Class may specify any number of different Constructors of which one (and only one)
may be niladic. This is also referred to as the default Constructor.
There may be any number of monadic Constructors, but each must have a differently
defined argument list which specifies the number of items expected in the Constructor
argument. See Section 4.2.2 for details.
The only way a Constructor function should be invoked is by ⎕NEW. See Section 4.2.6 for
further details. If you attempt to call a Constructor function from outside its Class, it
will cause a SYNTAX ERROR. A Constructor function should not call another Constructor
function within the same Class, although it will not generate an error. This would cause
the Base Constructor to be called twice, with unpredictable consequences.
When ⎕NEW is executed with a 2-item argument, the appropriate monadic Constructor
is called with the second item of the ⎕NEW argument.
The niladic (default) Constructor is called when ⎕NEW is executed with a 1-item
argument, a Class reference alone, or whenever APL needs to create a Section [Link]
for the Class.
Note that ⎕NEW first creates a new instance of the specified Class, and then executes
the Constructor inside the instance.
Example
The DomesticParrot Class defines a Constructor function egg that initialises the
Instance by storing its name (supplied as the 2nd item of the argument to ⎕NEW) in a
Public Field called Name.
∇ egg name
:Implements Constructor
:Access Public
Name←name
∇
...
:EndClass ⍝ DomesticParrot
In deciding which Constructor to call, APL matches the shape of the Constructor
argument with the signature of each of the Constructors that are defined. If a
constructor with the same number of arguments exists (remembering that 0 arguments
will match a niladic Constructor), it is called. If there is no exact match, and there is a
Constructor with a general signature (an un-parenthesised right argument), it is called.
If no suitable constructor is found, a LENGTH ERROR is reported.
There may be one and only one constructor with a particular signature.
The only way a Constructor function should be invoked is by ⎕NEW. See Section 4.2.6 for
further details. If you attempt to call a Constructor function from outside its Class, it
will cause a SYNTAX ERROR. A Constructor function should not call another Constructor
function within the same Class, although it will not generate an error. This would cause
the Base Constructor to be called twice, with unpredictable consequences.
In the following examples, the Make function (see for details) displays:
(⎕NEW Clover(,1)).Con
Make1 1
Creating an Instance with any other Constructor argument causes the system to choose
MakeAny.
(⎕NEW Clover(⍳10)).Con
10 MakeAny 1 2 3 4 5 6 7 8 9 10
(⎕NEW Clover(2 2⍴⍳4)).Con
2 2 MakeAny 1 2
3 4
Note that a scalar argument will call MakeAny and not Make1.
and finally, creating an Instance without a Constructor argument causes the system to
choose Make0.
(⎕NEW Clover).Con
Make0 0
:Class Bird
:Field Public Species
∇ egg spec
:Access Public Instance
:Implements Constructor
Species←spec
∇
∇ default
:Access Public Instance
:Implements Constructor
Species←'Default Bird'
∇
∇ R←Speak
:Access Public
R←'Tweet, tweet!'
∇
:EndClass ⍝ Bird
The niladic Constructor (in this example, the function default) is invoked when ⎕NEW is
called without Constructor arguments. In this case, the Instance created is no different
to one created by the monadic Constructor egg, except that the value of the Species
Field is set to 'Default Bird'.
Birdy←⎕NEW Bird
[Link]
Default Bird
The niladic Constructor is also used when APL needs to make a Section [Link] of the
Class. For example, in the expression (3↑Birdy), APL has to create two Section [Link]
of Birdy (one for each of the elements required to pad the array to length 3) and will in
fact call the niladic Constructor twice.
TweetyPie←3⊃10↑Birdy
The 10↑ (temporarily) creates a 10-element array comprising the single entity Birdy
padded with 9 fill-elements of Class Bird. To obtain the 9 fill-elements, APL calls the
niladic Constructor 9 times, one for each separate prototypical Instance that it is
required to make.
[Link]
Default Bird
:Class Cheese
:Field Public Name←''
:Field Public Strength←⍬
∇ make2(name strength)
:Access Public
:Implements Constructor
Name Strength←name strength
∇
∇ make1 name
:Access Public
:Implements Constructor
Name Strength←name 1
∇
∇ make_excuse
:Access Public
:Implements Constructor
⎕←'The cat ate the last one!'
∇
:EndClass
board←([Link]<3)/cheeses
[Link]
Caephilly Mild Cheddar
But look what happens when we try to select really strong cheese:
board←([Link]>5)/cheeses
[Link]
The cat ate the last one!
Note that this message is not the result of the expression, but was explicitly displayed
by the make_excuse function. The clue to this behaviour is the shape of board; it is
empty!
⍴board
0
[Link]
Notice that the behaviour of empty arrays of Instances is modelled VERY closely after
the behaviour of empty arrays in general. In particular, the Class designer is given the
task of deciding what the types of the members of the prototype are.
1. APL creates a new Instance of the same Class of which the empty Instance
belongs
2. the default (niladic) Constructor is run in the new Instance
3. the appropriate value is obtained or assigned:- if it is a reference is to a Field, the
value of the Field is obtained
4. if it is a reference is to a Property, the PropertyGet function is run
5. if it is a reference is to a Method, the method is executed
Example
:Class Bird
:Field Public Species
∇ egg spec
:Access Public Instance
:Implements Constructor
⎕DF Species←spec
∇
∇ default
:Access Public Instance
:Implements Constructor
⎕DF Species←'Default Bird'
#.DISPLAY Species
∇
∇ R←Speak
:Access Public
#.DISPLAY R←'Tweet, Tweet, Tweet'
∇
:EndClass ⍝ Bird
A reference to [Link] causes APL to create a new Instance and invoke the
niladic Constructor default. This function sets Species to 'Default Bird'and calls
#.DISPLAY which displays output to the Session.
DISPLAY [Link]
.→-----------.
|Default Bird|
'------------'
APL then retrieves the value of Species ('Default Bird'), applies the function
{0⍴⊂⍵} to it and returns this as the result of the expression.
.⊖---------------.
| .→-----------. |
| | | |
| '------------' |
'∊---------------'
A reference to [Link] causes APL to create a new Instance and invoke the niladic
Constructor default. This function sets Species to 'Default Bird'and calls
#.DISPLAY which displays output to the Session.
DISPLAY [Link]
.→-----------.
|Default Bird|
'------------'
APL then invokes function Speak which displays 'Tweet, Tweet, Tweet' and returns
this as the result of the function.
.→------------------.
|Tweet, Tweet, Tweet|
'-------------------'
APL then applies the function {0⍴⊂⍵} to it and returns this as the result of the
expression.
.⊖----------------------.
| .→------------------. |
| | | |
| '-------------------' |
'∊----------------------'
The statement:
calls a monadic Constructor in the Base Class. The choice of Constructor depends upon
the rank and shape of the result of expr (see Section 4.2.2 for details).
:Implements Constructor
or
Note that during the instantiation of an Instance, these calls potentially take place in
every Class in the Class hierarchy.
If, anywhere down the hierarchy, there is a monadic call and there is no matching
monadic Constructor, the operation fails with a LENGTH ERROR.
If there is a niladic call on a Class that defines no Constructors, the niladic call is simply
repeated in the next Class along the hierarchy.
A Constructor function may not call another Constructor function and a constructor
function may not be called directly from outside the Class or Instance. The only way a
Constructor function may be invoked is by ⎕NEW. The fundamental reason for these
restrictions is that there must be one and only one call on the Base Constructor when a
new Instance is instantiated. If Constructor functions were allowed to call one another,
there would be several calls on the Base Constructor. Similarly, if a Constructor could be
called directly it would potentially duplicate the Base Constructor call.
:Class Bird
:Field Public Desc
∇ egg0
:Access Public
:Implements Constructor
Desc←'Bird'
∇
:EndClass ⍝ Bird
(⎕NEW DomesticParrot).Desc
Bird→Parrot→DomesticParrot
Explanation
⎕NEW creates the new instance and runs the niladic Constructor DomesticParrot.egg0.
As soon as the line:
:Implements Constructor
is encountered, ⎕NEW calls the niladic constructor in the Base Class Parrot.egg0
:Implements Constructor
is encountered, ⎕NEW calls the niladic constructor in the Base Class Bird.egg0.
:Implements Constructor
is encountered, ⎕NEW cannot call the niladic constructor in the Base Class (there is
none) so the chain of Constructors ends. Then, as the state indicator unwinds ...
:Class Bird
:Field Public Species
∇ egg spec
:Access Public Instance
:Implements Constructor
Species←spec
∇
...
:EndClass ⍝ Bird
Explanation
⎕NEW creates the new instance and runs the Constructor [Link]. The egg
header splits the argument into two items name and species. As soon as the line:
is encountered, ⎕NEW calls the Base Class constructor [Link], passing it the result
of the expression to the right, which in this case is simply the value in species.
is encountered, ⎕NEW calls its Base Class constructor [Link], passing it the result of
the expression to the right, which in this case is the character vector 'Parrot: '
catenated with the value in species.
)SI
[#.[Instance of DomesticParrot]] #.[Link][3]*
[constructor]
:base
[#.[Instance of DomesticParrot]] #.[Link][2]
[constructor]
:base
[#.[Instance of DomesticParrot]] #.[Link][2]
[constructor]
4.2.9 Destructors
A Destructor is a function that is called just before an Instance of a Class ceases to exist
and is typically used to close files or release external resources associated with an
Instance.
Warning
Note that an Instance of a Class only disappears when the last reference to it
disappears. For example, the sequence:
I1←⎕NEW MyClass
I2←I1
)ERASE I1
will not cause the Instance of MyClass to disappear because it is still referenced by I2.
:Class Parrot
...
∇ kill
:Implements Destructor
'This Parrot is dead'
∇
...
:EndClass ⍝ Parrot
Note that reassignment to pol causes the Instance referenced by pol to be destroyed
and the Destructor invoked:
If a Class inherits from another Class, the Destructor in its Base Class is automatically
called after the Destructor in the Class itself.
So, if we have a Class structure: DomesticParrot => Parrot => Bird containing the
following Destructors:
:Class Bird
...
∇ kill
:Implements Destructor
'This Bird is dead'
∇
...
:EndClass ⍝ Bird
pol←⎕NEW DomesticParrot
)CLEAR
This Polly is dead
This Parrot is dead
This Bird is dead
clear ws
Methods are regular APL defined functions, but with some special characteristics that
control how they are called and where they are executed. Dfns may not be used as
Methods.
Fields are just like APL variables. To get the Field value, you reference its name; to set
the Field value, you assign to its name, and the Field value is stored in the Field.
However, Fields differ from variables in that they possess characteristics that control
their accessibility.
Properties are similar to APL variables. To get the Property value, you reference its
name; to set the Property value, you assign to its name. However, Property values are
actually accessed via PropertyGet and PropertySet functions that may perform all sorts
of operations. In particular, the value of a Property is not stored in the Property and
may be entirely dynamic.
All three types of member may be declared as Public or Private and as Instance or
Shared.
Public members are visible from outside the Class and Instances of the Class, whereas
Private members are only accessible from within.
Instance Members are unique to every Instance of the Class, whereas Shared Members
are common to all Instances and Shared Members may be referenced directly on the
Class itself.
4.3.2 Fields
Fields
To get the value of a Field, you reference its name; to set the value of a Field, you
assign to its name. Conceptually, the Field value is stored in the Field. However, Fields
differ from variables in that they possess characteristics that control their accessibility.
Note that Section 5.2.1 may be associated with Fields. See Section 5.2.4 for details.
Public Fields
A Public Field may be accessed from outside an Instance or a Class. Note that the
default is Private.
Class DomesticParrot has a Name Field which is defined to be Public and Instance (by
default).
∇ egg nm
:Access Public
:Implements Constructor
Name←nm
∇
...
:EndClass ⍝ DomesticParrot
pet←⎕NEW DomesticParrot'Polly'
[Link]
Polly
[Link]←⌽[Link]
[Link]
ylloP
Initialising Fields
A Field may be assigned an initial value. This can be specified by an arbitrary expression
that is executed when the Class is fixed by the Editor or by ⎕FIX.
∇ egg nm
:Access Public
:Implements Constructor
Name←nm
∇
...
:EndClass ⍝ DomesticParrot
[Link]
1
[Link]
Dicky
Note that if a Field is ReadOnly, this is the only way that it may be assigned a value.
Private Fields
A Private Field may only be referenced by code running inside the Class or an Instance
of the Class. Furthermore, Private Fields are not inherited.
The Section [Link] has a Private Instance Field named tie that is used to store the file
tie number in each Instance of the Class.
:Class ComponentFile
:Field Private Instance tie
∇ Open filename
:Implements Constructor
:Access Public Instance
:Trap 0
tie←filename ⎕FTIE 0
:Else
tie←filename ⎕FCREATE 0
:EndTrap
⎕DF filename,'(Component File)'
∇
As the field is declared to be Private, it is not accessible from outside an Instance of the
Class, but is only visible to code running inside.
Shared Fields
If a Field is declared to be Shared, it has the same value for every Instance of the Class.
Moreover, the Field may be accessed from the Class itself; an Instance is not required.
The following example establishes a Shared Field called Months that contains
abbreviated month names which are appropriate for the user's current International
settings. It also shows that an arbitrarily complex statement may be used to initialise a
Field.
:Class Example
:Using [Link]
:Field Public Shared ReadOnly Months←12↑(⎕NEW DateTimeFormatInfo).Ab
breviatedMonthNames
:EndClass ⍝ Example
EG←⎕NEW Example
[Link]
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov...
[Link]
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov...
Notice that in this case it is necessary to insert a :Using statement (or the equivalent
assignment to ⎕USING) in order to specify the .NET search path for the
DateTimeFormatInfo type. Without this, the Class would fail to fix.
You can see how the assignment works by executing the same statements in the
Session:
⎕USING←'[Link]'
12↑(⎕NEW DateTimeFormatInfo).AbbreviatedMonthNames
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov...
Trigger Fields
A field may act as a Trigger so that a function may be invoked whenever the value of
the Field is changed.
As an example, it is often useful for the Display Form of an Instance to reflect the value
of a certain Field. Naturally, when the Field changes, it is desirable to change the
Display Form. This can be achieved by making the Field a Trigger as illustrated by the
following example.
Notice that the Trigger function is invoked both by assignments made within the Class
(as in the assignment in ctor) and those made from outside the Instance.
:Class MyClass
:Field Public Name
:Field Public Country←'England'
∇ ctor nm
:Access Public
:Implements Constructor
Name←nm
∇
∇ format
:Implements Trigger Name,Country
⎕DF'My name is ',Name,' and I live in ',Country
∇
:EndClass ⍝ MyClass
[Link]←'Greece'
me
My name is Pete and I live in Greece
[Link]←'Kostas'
me
My name is Kostas and I live in Greece
4.3.3 Methods
Methods
Methods are implemented as regular defined functions, but with some special
attributes that control how they are called and where they are executed.
Public or Private
A Private method may only be invoked by another function that is running inside the
Class namespace or inside an Instance namespace. The name of a Private method is
not visible from outside the Class or an Instance of the Class.
A Public method may be called from outside the Class or an Instance of the Class.
Instance or Shared
An Instance method runs in the Instance namespace and may only be called via the
instance itself. An Instance method has direct access to Fields and Properties, both
Private and Public, in the Instance in which it runs.
A Shared method runs in the Class namespace and may be called via an Instance or via
the Class. However, a Shared method that is called via an Instance does not have direct
access to the Fields and Properties of that Instance.
Shared methods are typically used to manipulate Shared Properties and Fields or to
provide general services for all Instances that are not Instance specific.
Overridable Methods
A Method declared as being Overridable is replaced in situ (that is, within its own Class)
by a Method of the same name that is defined in a higher Class which itself is declared
with the Override keyword. See Section [Link].
Shared Methods
A Shared method runs in the Class namespace and may be called via an Instance or via
the Class. However, a Shared method that is called via an Instance does not have direct
access to the Fields and Properties of that Instance.
Class Parrot has a Speak method that does not require any information about the
current Instance, so may be declared as Shared.
:Class Parrot:Bird
∇ R←Speak times
:Access Public Shared
R←⍕times⍴⊂'Squark!'
∇
:EndClass ⍝ Parrot
wild←⎕NEW Parrot
[Link] 2
Squark! Squark!
Note that [Link] may be executed directly from the Class and does not in fact
require an Instance.
[Link] 3
Squark! Squark! Squark!
Instance Methods
An Instance method runs in the Instance namespace and may only be called via the
instance itself. An Instance method has direct access to Fields and Properties, both
Private and Public, in the Instance in which it runs.
Class DomesticParrot has a Speak method defined to be Public and Instance. Where
Speak refers to Name, it obtains the value of Name in the current Instance.
∇ egg nm
:Access Public
:Implements Constructor
Name←nm
∇
∇ R←Speak times
:Access Public Instance
R←⊂Name,', ',Name
R←↑R,times⍴⊂' Who''s a pretty boy, then!'
∇
:EndClass ⍝ DomesticParrot
pet←⎕NEW DomesticParrot'Polly'
[Link] 3
Polly, Polly
Who's a pretty boy, then!
Who's a pretty boy, then!
Who's a pretty boy, then!
bil←⎕NEW DomesticParrot'Billy'
[Link] 1
Billy, Billy
Who's a pretty boy, then!
Normally, a Method defined in a higher Class supersedes the Method of the same
name that is defined in its Base Class, but only for calls made from above or within the
higher Class itself (or an Instance of the higher Class). The base method remains
available in the Base Class and is invoked by a reference to it from within the Base
Class. This behaviour can be altered using the Overridable and Override key words in
the :Access statement but only applies to Instance Methods.
If a Public Instance method in a Class is marked as Overridable, this allows a Class which
derives from the Class with the Overridable method to supersede the Base Class
method in the Base Class, by providing a method which is marked Override. The typical
use of this is to replace code in the Base Class which handles an event, with a method
provided by the derived Class.
For example, the base class might have a method which is called if any error occurs in
the base class:
∇ ErrorHandler
[1] :Access Public Overridable
[2] ⎕←↑⎕DM
∇
In your derived class, you might supersede this by a more sophisticated error handler,
which logs the error to a file:
∇ ErrorHandler;TN
[1] :Access Public Override
[2] ⎕←↑⎕DM
[3] TN←'ErrorLog'⎕FSTIE 0
[4] ⎕DM ⎕FAPPEND TN
[5] ⎕FUNTIE TN
∇
If the derived class had a function which was not marked Override, then function in the
derived class which called ErrorHandler would call the function as defined in the
derived class, but if a function in the base class called ErrorHandler, it would still see
the base class version of this function. With Override specified, the new function
supersedes the function as seen by code in the base class. Note that different derived
classes can specify different Overrides.
In C#, Java and some other compiled languages, the term Virtual is used in place of
Overridable, which is the term used by Visual Basic and Dyalog APL.
4.3.4 Properties
Properties
A Property behaves in a very similar way to an ordinary APL variable. To obtain the
value of a Property, you simply reference its name. To change the value of a Property,
you assign a new value to the name.
However, under the covers, a Property is accessed via a PropertyGet function and its
value is changed via a PropertySet function. Furthermore, Properties may be defined to
allow partial (indexed) retrieval and assignment to occur.
There are three types of Property, namely Simple, Numbered and Keyed.
• A Simple Property is one whose value is accessed (by APL) in its entirety and re-
assigned (by APL) in its entirety.
• A Numbered Property behaves like an array (conceptually a vector) which is only
ever partially accessed and set (one element at a time) via indices. The
Numbered Property is designed to allow APL to perform selections and
structural operations on the Property.
• A Keyed Property is similar to a Numbered Property except that its elements are
accessed via arbitrary keys instead of indices.
The following cases illustrate the difference between Simple and Numbered Properties.
If Instance MyInst has a Simple Property Sprop and a Numbered Property Nprop, the
expressions
X←[Link]
X←[Link][2]
both cause APL to call the PropertyGet function to retrieve the entire value of Sprop.
The second statement subsequently uses indexing to extract just the second element of
the value.
X←[Link][2]
causes APL to call the PropertyGet function with an additional argument which
specifies that only the second element of the Property is required. Moreover, the
expression:
X←[Link]
causes APL to call the PropertyGet function successively, for every element of the
Property.
• one or more :Access statements which must appear first in the body of the
Property.
• a single PropertyGet function.
• a single PropertySet function
• a single PropertyShape function
A Simple Instance Property is one whose value is accessed (by APL) in its entirety and
re-assigned (by APL) in its entirety. The following examples are taken from the .
:Property Count
:Access Public Instance
∇ r←get
r←¯1+2⊃⎕FSIZE tie
∇
:EndProperty ⍝ Count
Because there is no set function defined, the Property is read-only and attempting to
change it causes SYNTAX ERROR.
[Link]←99
SYNTAX ERROR
[Link]←99
^
The Access Property has both get and set functions which are used, in this simple
example, to get and set the component file access matrix.
:Property Access
:Access Public Instance
∇ r←get
r←⎕FRDAC tie
∇
∇ set am;mat;OK
mat←[Link]
:Trap 0
OK←(2=⍴⍴mat)^(3=2⊃⍴mat)^^/,mat=⌊mat
:Else
OK←0
:EndTrap
'bad arg'⎕SIGNAL(~OK)/11
mat ⎕FSTAC tie
∇
:EndProperty ⍝ Access
Note that the set function must be monadic. Its argument, supplied by APL, will be an
Instance of PropertyArguments. This is an internal Class whose NewValue field
contains the value that was assigned to the Property.
Note too that the set function does not have to accept the new value that has been
assigned. The function may validate the value reject or accept it (as in this example), or
perform whatever processing is appropriate.
[Link]←'junk'
bad arg
[Link]←'junk'
^
[Link]←1 2⍴10
bad arg
[Link]←1 2⍴10
^
The specifies a Simple Shared Property named Files which returns the names of all
the Component Files in the current directory.
The previous examples have illustrated the use of Instance Properties. It is also possible
to define Shared properties.
A Shared property may be used to handle information that is relevant to the Class as a
whole, and which is not specific to any a particular Instance.
:Property Files
:Access Public Shared
∇ r←get
r←⎕FLIB''
∇
:EndProperty
Note that ⎕FLIB (invoked by the Files get function) does not report the names of tied
files.
Note that a Shared Property may be accessed from the Class itself. It is not necessary to
create an Instance first.
[Link]
test1
test2
Numbered Properties
Numbered Properties
A Numbered Property behaves like an array (conceptually a vector) which is only ever
partially accessed and set (one element at a time) via indices.
If the expression uses indexing, APL checks that the index or indices are within the
bounds of these dimensions, and then calls the PropertyGet or PropertySet function. If
the expression specifies a single index, APL calls the PropertyGet or PropertySet
function once. If the expression specifies multiple indices, APL calls the function
successively.
If the expression references or assigns the entire Property (without indexing) APL
generates a set of indices for every element of the Property and calls the PropertyGet
or PropertySet function successively for every element in the Property.
Note that APL generates a RANK ERROR if an index contains the wrong number of
elements or an INDEX ERROR if an index is out of bounds.
Example
The specifies a Numbered Property named Component which represents the contents
of a specified component on the file.
[Link]¨(⍳5)×⊂⍳4
1 2 3 4 5
[Link]
5
[Link][4]
4 8 12 16
4⊃[Link]
4 8 12 16
(⊂4 3)⌷[Link]
4 8 12 16 3 6 9 12
Referencing a Numbered Property in its entirety causes APL to call the get function
successively for every element.
[Link]
1 2 3 4 2 4 6 8 3 6 9 12 4 8 12 16 5 10 15 20
[Link][3]
World
[Link][6]
INDEX ERROR
[Link][6]
^
[Link][1;2]
RANK ERROR
[Link][1;2]
^
A single Numbered Property may be identified as the Default Property for the Class. If a
Class has a Default Property, indexing with the ⌷ primitive function and [...] indexing
may be applied to the Property directly via a reference to the Class or Instance.
The Numbered Property example of the can be extended by adding the control word
Default to the :Property statement for the Component Property.
Indexing may now be applied directly to the Instance F1. In essence, F1[n] is simply
shorthand for [Link][n] and n⌷F1 is shorthand for n⌷[Link]
F1[4]
4 8 12 16
(⊂4 3)⌷F1
4 8 12 16 3 6 9 12
((⊂4 3)⌷F1)←'Hello' 'World'
F1[3]
World
4⊃F1
DOMAIN ERROR
4⊃F1
^
ComponentFile Class
:Class ComponentFile
:Field Private Instance tie
∇ Open filename
:Implements Constructor
:Access Public Instance
:Trap 0
tie←filename ⎕FTIE 0
:Else
tie←filename ⎕FCREATE 0
:EndTrap
⎕DF filename,'(Component File)'
∇
∇ Close
:Access Public Instance
⎕FUNTIE tie
∇
∇ r←Append data
:Access Public Instance
r←data ⎕FAPPEND tie
∇
∇ Replace(comp data)
:Access Public Instance
data ⎕FREPLACE tie,comp
∇
:Property Count
:Access Public Instance
∇ r←get
r←¯1+2⊃⎕FSIZE tie
∇
:EndProperty ⍝ Count
:Property Access
:Access Public Instance
∇ r←get arg
r←⎕FRDAC tie
∇
∇ set am;mat;OK
mat←[Link]
:Trap 0
OK←(2=⍴⍴mat)^(3=2⊃⍴mat)^^/,mat=⌊mat
:Else
OK←0
:EndTrap
'bad arg'⎕SIGNAL(~OK)/11
mat ⎕FSTAC tie
∇
:EndProperty ⍝ Access
:Property Files
:Access Public Shared
∇ r←get
r←⎕FLIB''
∇
:EndProperty
∇ Delete file;tie
:Access Public Shared
tie←file ⎕FTIE 0
file ⎕FERASE tie
∇
:EndClass ⍝ Class ComponentFile
Keyed Properties
Keyed Properties
To implement a Keyed Property, only a get and/or a set function are required. APL
does not attempt to validate or resolve the specified indices in any way, so does not
require the presence of a shape function for the Property.
However, APL does check that the rank and lengths of the indices correspond to the
rank and lengths of the array to the right of the assignment (for an indexed assignment)
and the array returned by the get function (for an indexed reference). If the rank or
shape of these arrays fails to conform to the rank or shape of the indices, APL will issue
a RANK ERROR or LENGTH ERROR.
Note too that indices may be elided. If KProp is a Keyed Property of Instance I1, the
following expressions are all valid.
[Link]
[Link][]←10
[Link][;]←10
[Link]['One' 'Two';]←10
[Link][;'One' 'Two']←10
When APL calls a monadic get or a set function, it supplies an argument of type
PropertyArguments, which identifies which dimensions and indices were specified. See
Section 4.11.2.
The Sparse2 Class illustrates the implementation and use of a Keyed Property.
SA1←⎕NEW Sparse2
[Link][⊂'Widgets';⊂'Jan']←100
[Link][⊂'Widgets';⊂'Jan']
100
[Link]['Widgets' 'Grommets';'Jan' 'Mar' 'Oct']←10×2 3⍴⍳6
[Link]['Widgets' 'Grommets';'Jan' 'Mar' 'Oct']
10 20 30
40 50 60
[Link][⊂'Widgets';'Jan' 'Oct']
10 30
[Link]['Grommets' 'Widgets';⊂'Oct']
60
30
∇ k←fixkeys k
k←(2≠≡¨k){,(⊂⍣⍺)⍵}¨k
k←⊃(∘.{⊃,/⊂¨⍺ ⍵})/k
∇
:EndClass ⍝ 2D Sparse Array
Internally, Sparse2 maintains a list of keys and a list of values which are initialised to
empty arrays by its constructor.
When an indexed assignment is made, the set function receives a list of keys (indices)
in [Link] and values in [Link]. The function updates the values of
existing keys, and adds new keys and their values to the internal lists.
When an indexed reference is made, the get function receives a list of keys (indices) in
[Link]. The function uses these keys to retrieve the corresponding values,
inserting 0s for non-existent keys.
.→-----------------------------------------------.
| .→---------------------. .→------------------. |
| | .→------. .→-------. | | .→--. .→--. .→--. | |
| | |Widgets| |Grommets| | | |Jan| |Mar| |Oct| | |
| | '-------' '--------' | | '---' '---' '---' | |
| '∊---------------------' '∊------------------' |
'∊-----------------------------------------------'
Example 2
∇ Open filename
:Implements Constructor :Base filename
:Access Public Instance
:If Count>0
Keys←{⊃⍵⊃⎕[Link]}¨⍳Count
:Else
Keys←0⍴⊂''
:EndIf
∇
3
[Link]['Geoff' 'Pete']
1 2 3 4 42
5 6 7 8
9 10 11 12
[Link]['Pete' 'Morten']←(3 4⍴'∘')(⍳⍳3)
[Link]
4
[Link]['Morten' 'Pete' 'John']
1 1 1 1 1 2 1 1 3 ∘∘∘∘ 1 2 3 4 5 6 7 8 9 10
1 2 1 1 2 2 1 2 3 ∘∘∘∘
∘∘∘∘
4.4 Interfaces
4.4.1 Interfaces
An Interface is defined by a Script that contains skeleton declarations of Properties
and/or Methods. These members are only place-holders; they have no specific
implementation; this is provided by each of the Classes that support the Interface.
Similarly, to implement a Property the type (Simple, Numbered or Keyed) and syntax
(defined by the presence or absence of a PropertyGet and PropertySet functions) must
exactly match that of the property described in the Interface. The Property name,
however, need not be the same as that described in the Interface.
In this case, the Penguin Class derives from Animal but additionally supports the
BirdBehaviour and FishBehaviour Interfaces, thereby inheriting members from both.
Pingo←⎕NEW Penguin
⎕CLASS Pingo
#.Penguin #.FishBehaviour #.BirdBehaviour #.Animal
To import methods from a Namespace NS, the Class Script must include a statement:
:Include NS
When the Class is fixed by the editor or by ⎕FIX, all the defined functions and operators
in Namespace NS are included as methods in the Class. The functions and operators
which are brought in as methods from the namespace NS are treated exactly as if the
source of each function/operator had been included in the class script at the point of
the :Include statement. For example, if a function contains :Signature or :Access
statements, these will be taken into account. Note that such declarations have no effect
on a function/operator which is in an ordinary namespace.
Dfns and dops in NS are also included in the Class but as Private members, because dfns
and dops may not contain :Signature or :Access statements. Variables and Sub-
namespaces in NS are not included.
Note that objects imported in this way are not actually copied, so there is no penalty
incurred in using this feature. Additions, deletions and changes to the functions in NS
are immediately reflected in the Class.
If there is a member in the Class with the same name as a function in NS, the Class
member takes precedence and supersedes the function in NS.
Conversely, functions in NS will supersede members of the same name that are
inherited from the Base Class, so the precedence is:
Class supersedes
Base Class
Any number of Namespaces may be included in a Class and the :Include statements
may occur anywhere in the Class script. However, for the sake of readability, it is
recommended that you have :Include statements at the top, given that any
definitions in the script will supersede included functions and operators.
For information on copying classes that reference namespaces in this way, see Dyalog
APL Language: Copy.
4.5.2 Example
In this example, Class Penguin inherits from Animal and includes functions from the
plain Namespaces BirdStuff and FishStuff.
:Namespace BirdStuff
∇ R←Fly
:Access Public Instance
R←'Fly, Fly ...'
∇
∇ R←Lay
:Access Public Instance
R←'Lay, Lay ...'
∇
:EndNamespace ⍝ BirdStuff
:Namespace FishStuff
∇ R←Swim
:Access Public Instance
R←'Swim, Swim ...'
∇
:EndNamespace ⍝ FishStuff
Pingo←⎕NEW Penguin
[Link]
Swim, Swim ...
[Link]
Lay, Lay ...
[Link]
Fly, Fly ...
This is getting silly - we all know that Penguin's can't fly. This problem is simply resolved
by overriding the [Link] method with [Link]. We can hide
[Link] with a Private method in Penguin that does nothing. For example:
Pingo←⎕NEW Penguin
[Link]
VALUE ERROR
[Link]
^
Pingo←⎕NEW Penguin
[Link]
Sadly, I cannot fly
A Public Nested Class is visible from outside its containing Class and may be used
directly in its own right, whereas a Private Nested Class is not and may only be used
by code inside the containing Class.
However, methods in the containing Class may return instances of Private Nested
Classes and in that way expose them to the calling environment.
GolfService contains the following nested classes, all of which are Private.
A Class that represents a Golf Course, having Fields Code and Name .
A Class that represents a tee-time or match, having Fields Time and Players .
Up to 4 players may play together in a match.
A Class that represents a reservation for a particular tee-time at a particular golf
course. This has Fields OK , Course , TeeTime and Message . The value of
TeeTime is an Instance of a Slot Class.
The GolfService constructor takes the name of a file in which all the data is stored. This
file is initialised by method InitFile if it doesn't already exist.
The method returns an array of Instances of the internal (nested) Class . Notice how
the display form of each Instance is established by the constructor, to obtain the output
display shown below.
[Link]
St Andrews(1) Hindhead(2) Basingstoke(3)
All of the dates and times employ instances of the .NET type [Link], and the
following statements just set up some temporary variables for convenience later.
⎕←Tomorrow←(⎕NEW DateTime(3↑⎕TS)).AddDays 1
31/03/2006 [Link]
⎕←TomorrowAt7←[Link] 7
31/03/2006 [Link]
• the code for the golf course at which the reservation is required
• the date and time of the reservation
• a flag to indicate whether or not the nearest available time will do
• the code for the golf course at which the reservation is required
The result is an Instance of the internal Class . Once again, ⎕DF is used to make the
default display of these Instances meaningful. In this case, the reservation is successful.
Bob, Arnie and Jack also ask to play at 7:00 but are given the 7:10 tee-time instead (4-
player restriction).
However, Pete and Tiger are joined at 7:00 by Dave and Al.
Up to now, all bookings have been made with the tee-time flexibility flag set to 1.
Inflexible Jim is only interested in playing at 7:00...
Finally the method is used to obtain an Instance of the internal Class for the given
course and day.
[Link] 2 Tomorrow
Hindhead(2) 31/03/2006 [Link]
31/03/2006 [Link] Pete Tiger Dave Al
31/03/2006 [Link] Bob Arnie Jack
31/03/2006 [Link]
....
The names of Classes defined within a Namespace Script which are parents, children,
or siblings are visible both to one another and to code and expressions defined in the
same script, regardless of the namespace hierarchy within it. Names of Classes which
are nieces or nephews and their descendants are however not visible.
For example:
:Namespace a
d←⎕NEW a1
e←⎕NEW bb2
:Class a1
∇ r←foo
:Access Shared Public
r←⎕NEW¨b1 b2
∇
:EndClass ⍝ a1
∇ r←goo
r←[Link]
∇
∇ r←foo
r←⎕NEW¨b1 b2
∇
:Namespace b
:Class b1
:EndClass ⍝ b1
:Class b2
:Class bb2
:EndClass ⍝ bb2
:EndClass ⍝ b2
:EndNamespace ⍝ b
:EndNamespace ⍝ a
a.d
#.a.[a1]
a.e
#.a.[bb2]
[Link]
#.a.[b1] #.a.[b2]
Note that the names of Classes b1 (a.b.b1) and b2 (a.b.b2) are not visible from their
"uncle" a1 (a.a1).
[Link]
VALUE ERROR
foo[2] r←⎕NEW¨b1 b2
Notice that Classes in a Namespace Script are fixed before other objects (hence the
assignments to d and e are evaluated after Classes a1 and bb2 are fixed), although the
order in which Classes themselves are defined is still important if they reference one
another during initialisation.
The source of a scripted object can only be altered using the Editor, or by refixing it in
its entirety using ⎕FIX. Dynamic changes to variables, fields and properties, and calling
⎕FX to generate functions do not alter the source of a scripted object.
Furthermore, if you introduce new objects of any type (functions, variables, or classes)
into a namespace or a class defined by a script by any means other than editing the
script, then these objects will be lost the next time the script is edited and fixed.
If you fix a function using ⎕FX with the same name as a function defined in the script,
this new version will supercede the version defined from the script, although the
version in the script will remain unchanged.
If you edit the function (as opposed to editing the script) the Editor will show the new
version of the function.
If however you edit the script, the Editor will display the original version of the function
embedded in the script.
If you were to edit both the script and the function, the Editor would show the two
different versions of the function as illustrated in the example that follows.
When you fix the script, the version of the function in the script will replace the one
created using ⎕FX.
Example
:Namespace ns
∇ foo
1
∇
:EndNamespace
[Link]
1
Note that the Editor displays the description Unscripted Function in the status bar of
the window showing the new version of foo.
Similarly, if you were to Trace the execution of [Link], the Tracer would display the
current (⎕FX'ed) version of foo, with the same description in its status bar.
Diary contains a (private) Field named entries, which is simply a vector of instances
of DiaryEntry. These are 2-element vectors containing a .NET DateTime object and a
description.
The entries Field is referenced through the Entry Property, which is defined as the
Section [Link]. This allows individual entries to be referenced and changed using
indexing on a Diary Instance.
Note that DiaryEntry is defined in the script first (before Diary) because it is
referenced by the initialisation of the [Link] Field
D←⎕NEW [Link]
[Link](2006 4 30 10 0)'Dentist'
30/04/2006 [Link] Dentist
One of the benefits of the Namespace Script is that Classes defined within it (which are
typically related) may be used independently, so we can create a stand-alone instance
of DiaryEntry; "Doctor at 11:00"...
... and then use it to replace the second Diary entry with indexing:
D[2]←Doc
D[2]
30/04/2006 [Link] Doctor
[Link] 2006 4 30
30/04/2006 [Link] Meeting with John ...
... 30/04/2006 [Link] Doctor
[Link] 2006 4 30 11 0
1
⌷D
30/04/2006 [Link] Meeting with John
:Require [Link]
If no path is specified, the path is taken to be relative to the current script file or, if in a
workspace script, the current working directory. Note that a leading './' or '.\' in
path is not allowed, to avoid any potential confusion with "current directory".
:Require is a directive to the Editor (more specifically, to the internal mechanism that
fixes a script as an object in the workspace) and can appear in any script containing APL
code, but must precede all code in the script. :Require is thus not valid within a
function, class, namespace or any other definition.
The prefix file:// allows for the possibility of a future extension of https:// and
[Link]
In version 20.0 ⍝!:require is a synonym for :Require. This allows the user to create
scripts which can be used in multiple versions of Dyalog; in 14.1 and earlier SALT parses
⍝!:require statements and loads the appropriate files, in 20.0 it is the interpreter
loads the file named in ⍝!:require statements. Dyalog intends to remove support for
the ⍝!:require statement from the interpreter in a future version. Note that unlike
:Require, ⍝!:require can appear within code.
Properties and Methods defined in an Interface, and the Class functions that
implement the Interface, may not contain :Access Statements.
A Namespace script must begin with a :Namespace statement and end with a
:EndNamespace statement.
Classes are defined by pairs of :Class and :EndClass statements within the
Namespace script, and these too may be nested.
The names of Classes defined within a Namespace Script are visible both to one
another and to code and expressions defined in the same script, regardless of the
namespace hierarchy within it.
A Namespace script is therefore particularly useful to group together Classes that refer
to one another where the use of nested classes is inappropriate.
:Include <namespace>
...
:EndClass
A class script begins with a :Class statement and ends with a :EndClass statement.
The elements that comprise the :Class statement are as follows:
Element Description
Optionally, specifies the name of the Class, which must conform
class name
to the rules governing APL names.
base class Optionally specifies the name of a Class from which this Class is
name derived and whose members this Class inherits.
interface
The names of one or more Interfaces which this Class supports.
name
A Class may import methods defined in separate plain Namespaces with one or more
:Include statements. For further details, see Section 4.5.1.
Examples
The following statements define a Class named Penguin that derives from (is based
upon) a Class named Animal and which supports two Interfaces named BirdBehaviour
and FishBehaviour.
The following statements define a Class named Penguin that derives from (is based
upon) a Class named Animal and includes methods defined in two separate
Namespaces named BirdStuff and FishStuff.
Element Description
NameSpace Specifies a .NET namespace.
Specifies the Assembly in which NameSpace is located. If the
Assembly is located in the [Link] installation directory, you
Assembly
need only specify its name. If not, you must specify a full or relative
pathname.
When the class is fixed, ⎕USING is inherited from the surrounding space. Each :Using
statement appends an element to ⎕USING, with the exception of :Using with no
argument:
If you omit <Namespace>, this is equivalent to clearing ⎕USING, which means that
no .NET namespaces will be searched (unless you follow this statement with additional
:Using statements, each of which will append to ⎕USING).
To set ⎕USING, to a single empty character vector, which only allows references to fully
qualified names of classes in [Link], you must write:
or
:Using ,[Link]
that is, specify an empty namespace name followed by no assembly, or followed by the
default assembly, which is always loaded.
Attributes are descriptive tags that provide additional information about programming
elements. Attributes are not used by Dyalog APL but other applications can refer to the
extra information in attributes to determine how these items can be used. Attributes
are saved with the metadata of Dyalog APL .NET assemblies.
Element Description
Name The name of a .NET attribute
ConstructorArgs Optional arguments for the Attribute constructor
Example
:Class c1
:using System
:attribute SerializableAttribute
:attribute CLSCompliantAttribute 1
∇ foo(p1 p2)
:Access public instance
:Signature foo Object,Object
:Attribute ObsoleteAttribute
∇
∇ goo(p1 p2)
:Access public instance
:Signature foo Object,Object
:Attribute ObsoleteAttribute 'Don''t use this' 1
:EndClass ⍝ c1
When this Class is exported as a .NET Class, the attributes are saved in its metadata. For
example, Visual Studio will warn developers if they make use of a member which has
the ObsoleteAttribute.
The :Access statement is used to specify characteristics for Classes, Properties and
Methods.
Element Description
Specifies whether or not the (nested) Class, Property or Method
Private|
is accessible from outside the Class or an Instance of the Class.
Public
The default is Private .
For a Field, specifies if there is a separate value of the Field in
each Instance of the Class, or if there is only a single value that is
Instance|
shared between all Instances. For a Property or Method, specifies
Shared
whether the code associated with the Property or Method runs
in the Class or Instance.
Applies only to a Method and specifies that the method is
WebMethod exported as a web method. This applies only to a Class that
implements a Web Service.
Applies only to an Instance Method and specifies that the
Overridable Method may be overridden by a Method in a higher Class. See
below.
Applies only to an Instance Method and specifies that the
Override Method overrides the corresponding Overridable Method
defined in the Base Class. See below.
Overridable/Override
Normally, a Method defined in a higher Class replaces a Method of the same name that
is defined in its Base Class, but only for calls made from above or within the higher
Class itself (or an Instance of the higher Class). The base method remains available in
the Base Class and is invoked by a reference to it from within the Base Class.
However, a Method declared as being Overridable is replaced in situ (that is, within its
own Class) by a Method of the same name in a higher Class if that Method is itself
declared with the Override keyword. For further information, see Section [Link].
Nested Classes
The :Access statement is also used to control the visibility of one Class that is defined
within another (a nested Class). A Nested Class may be either Private or Public. Note
that the :Access Statement must precede the definition of any Class contents.
A Public Nested Class is visible from outside its containing Class and may be used
directly in its own right, whereas a Private Nested Class is not and may only be used
by code inside the containing Class.
However, methods in the containing Class may return instances of Private Nested
Classes and in that way expose them to the calling environment.
WebMethod
:Access Public
:Attribute [Link]
Element Description
Constructor Specifies that the function is a Class Constructor .
Specifies that the Base Constructor be called with the result of the
:Base expr
expression expr as its argument.
Destructor Specifies that the function is a Class Destructor .
Specifies that the function implements the Method MethodName
Method
whose syntax is specified by Interface InterfaceName .
Identifies the function as a Trigger Function which is activated by
changes to variable name1, name2, and so forth. Trigger *
Trigger
specifies a Global Trigger that is activated by the assignment of
any global variable in the same namespace.
Element Description
Private| Specifies whether or not the Field is accessible from outside the
Public Class or an Instance of the Class. The default is Private .
Specifies if there is a separate value of the Field in each Instance
Instance|
of the Class, or if there is only a single value that is shared
Shared
between all Instances.
If specified, this keyword prevents the value in the Field from
ReadOnly
being changed after initialisation.
If specified, this identifies a .Net type for the Field. This type
Type
applies only when the Class is exported as a .NET Assembly.
FieldName Specifies the name of the Field (mandatory).
← expr Specifies an initial value for the Field.
Examples
The following statement defines a Field called Name. It is (by default), an Instance Field
so every Instance of the Class has a separate value. It is a Public Field and so may be
accessed (set or retrieved) from outside an Instance.
Months is a Shared Field so there is just a single value that is the same for every
Instance of the Class. It is (by default), a Private Field and may only be referenced by
code running in an Instance or in the Class itself. Furthermore, it is ReadOnly and may
not be altered after initialisation. Its initial value is calculated by an expression that
obtains the short month names that are appropriate for the current locale using
the .NET Type DateTimeFormatInfo.
Notes
Note that Fields are initialised when a Class script is fixed by the editor or by ⎕FIX. If
the evaluation of expr causes an error (for example, a VALUE ERROR), an appropriate
message will be displayed in the Status Window and ⎕FIX will fail with a DOMAIN
ERROR. Note that a ReadOnly Field may only be assigned a value by its :Field
statement.
In the second example above, the expression will only succeed if ⎕USING is set to the
appropriate path, in this case [Link].
You may not define a Field with the name of one of the permissible keywords (such as
public). In such cases the Class will not fix and an error message will be displayed in
the Status Window. For example:
error AC0541: a field must have a name " :Field Public public"
Element Description
Specifies the name of the Property by which it is accessed.
Additional Properties, sharing the same PropertyGet and/or
Name
PropertySet functions, and the same access behaviour may be
specified by a comma-separated list of names.
Simple|
Numbered| Specifies the type of Property (see below). The default is Simple .
Keyed
Specifies that this Property acts as the default property for the
Default Class when indexing is applied directly to an Instance of the
Class.
Private| Specifies whether or not the Property is accessible from outside
Public the Class or an Instance of the Class. The default is Private .
Specifies if there is a separate value of the Property in each
Instance|
Instance of the Class, or if there is only a single value that is
Shared
shared between all Instances.
A Section [Link] is one whose value is accessed (by APL) in its entirety and re-assigned
(by APL) in its entirety.
A Section [Link].1 behaves like an array (conceptually a vector) which is only ever
partially accessed and set (one element at a time) via indices.
A Section [Link].1 is similar to a Numbered Property except that its elements are
accessed via arbitrary keys instead of indices.
Numbered and Keyed Properties are designed to allow APL to perform selections and
structural operations on the Property.
The three functions are identified by case-independent names Get, Set and Shape.
Errors
When a Class is fixed by the Editor or by ⎕FIX, APL checks the validity of each Property
section and the syntax of Section 4.11.3, Section 4.11.4 and Section 4.11.5 functions
within them.
• You may not specify a name which is the same as one of the keywords.
• There must be at least a Section 4.11.3, or a Section 4.11.4 or a Section 4.11.5
function defined.
• You may only define a Section 4.11.5 function if the Property is Numbered.
If anything is wrong, the Class is not fixed and an error message is displayed in the
Status Window. For example:
The PropertyGet function must be result returning. For a Simple Property, it may be
monadic or niladic. For a Numbered or Keyed Property it must be monadic.
The result R may be any array. However, for a Keyed Property, R must conform to the
rank and shape specified by [Link] or be scalar.
In all cases, [Link] contains the name of the Property being referenced and
NewValue is undefined (VALUE ERROR).
Note
The PropertySet function must be monadic and may not return a result.
In all cases, [Link] contains the name of the Property being referenced and
NewValue contains the new value(s) for the element(s) of the Property being assigned.
Note
The PropertyShape function must be niladic or monadic and must return a result.
If monadic, ipa is an instance of the internal class Section 4.11.2. [Link] contains
the name of the Property being referenced and NewValue and Indexers are undefined
(VALUE ERROR).
The result R must be an integer vector or scalar that specifies the rank of the Property.
Each element of R specifies the length of the corresponding dimension of the Property.
Otherwise, the reference or assignment to the Property will fail with DOMAIN ERROR.
Note that the result R is used by APL to check that the number of indices corresponds
to the rank of the Property and that the indices are within the bounds of its
dimensions. If not, the reference or assignment to the Property will fail with RANK
ERROR or LENGTH ERROR.
Note
A callback function is associated with a particular event via the Event property of the
object concerned. A callback function is executed by ⎕DQ when the event occurs, or by
⎕NQ.
If you append the character & to the name of the callback function in the Event
specification, the callback function will be executed asynchronously as a thread when
the event occurs. If not, it is executed synchronously as before.
tells ⎕DQ to execute the callback function DoIt asynchronously as a thread when a
Select event occurs on the object.
Warning
The interpreter may switch between running threads at the following points:
At any of these points, the interpreter might execute code in other threads. If such
threads change the global environment; for example by changing the value of, or
expunging a name; then the changes will appear to have happened while the thread in
question passes through the switch point. It is the task of the application programmer
to organise and contain such behaviour.
You can prevent threads from interacting in critical sections of code by using the :Hold
control structure.
Note that the interpreter cannot perform thread-switching during the execution of a
high-priority callback. This is a callback function that is invoked by a high-priority event
which demands that the interpreter must return a result to Windows before it may
process any other event. Such high-priority events include Configure, ExitWindows,
DateTimeChange, DockStart, DockCancel, DropDown. It is therefore not permitted to
use a :Hold control structure in a high-priority callback function.
Just as with a synchronous call, a function called asynchronously has its own local
environment, but can communicate with its parent and "sibling" functions via local
names in the parent.
This point is important. It means that siblings can run in parallel without danger of local
name clashes. For example, a GUI application can accommodate multiple concurrent
instances of its callback functions.
However, with an asynchronous call, as the calling function continues to execute, both
child and parent functions may modify values in the calling environment. Both
functions see such changes immediately they occur.
If a parent function terminates while any of its children are still running, those children
will no longer have access to its local names, and references to such names will either
generate VALUE ERROR or be replaced by values from the environment that called the
parent function. If a child function references variables defined by its parent or relies in
any other way on its parent's environment (such as a local value of ⎕IO), the parent
function should therefore execute a ⎕TSYNC in order to wait for its children to complete
before itself exiting.
If, on the other hand, after launching an asynchronous child, the parent function calls a
new function (either synchronously or asynchronously); names in the new function are
beyond the purview of the original child. In other words, a function can only ever see
its calling stack decrease in size – never increase. This is in order that the parent may
call new defined functions without affecting the environment of its asynchronous
children.
Suppose that you had the following functions: RUN[3] calls INIT which in turn calls
GETDATA but as 3 separate threads with 3 different arguments:
∇ RUN;A;B
[1] A←1
[2] B←'Hello World'
[3] INIT
[4] CALC
[5] REPORT
∇
∇ INIT;C;D
[1] C←D←0
[2] GETDATA&'Sales'
[3] GETDATA&'Costs'
[4] GETDATA&'Expenses'
∇
When each GETDATA thread starts, it immediately sees (via ⎕SI) that it was called by
INIT which was in turn called by RUN, and it sees local variables A, B, C and D. However,
once INIT[4] has been executed, INIT terminates, and execution of the root thread
continues by calling CALC. From then on, each GETDATA thread no longer sees INIT (it
thinks that it was called directly from RUN) nor can it see the local variables C and D that
INIT had defined. However, it does continue to see the locals A and B defined by RUN,
until RUN itself terminates.
Note that if CALC were also to define locals A and B, the GETDATA threads would still see
the values defined by RUN and not those defined by CALC. However, if CALC were to
modify A and B (as globals) without localising them, the GETDATA threads would see the
modified values of these variables, whatever they happened to be at the time.
∇ BUG;SEMI_GLOBAL
[1] SEMI_GLOBAL←0
[2] FOO& 1
[3] GOO& 1
∇
∇ FOO
[1] :If SEMI_GLOBAL=0
[2] DO_SOMETHING SEMI_GLOBAL
[3] :Else
[4] DO_SOMETHING_ELSE SEMI_GLOBAL
[5] :EndIf
∇
∇ GOO
[1] SEMI_GLOBAL←1
∇
In this example, it is formally impossible to predict in which order APL will execute
statements in BUG, FOO or GOO from BUG[2] onwards. For example, the actual sequence
of execution may be:
or
This is because APL may switch from one thread to another between any two lines in a
defined function. In practice, because APL gives each thread a significant time-slice, it is
likely to execute many lines, maybe even hundreds of lines, in one thread before
switching to another. However, you must not rely on this; thread-switching may occur
at any time between lines in a defined function.
Secondly, consider the possibility that APL switches from the FOO thread to the GOO
thread after FOO[1]. If this happens, the value of SEMI_GLOBAL passed to
DO_SOMETHING will be 1 and not 0. Here is another source of error.
In this case, there are two ways to resolve the problem. To ensure that the value of
SEMI_GLOBAL remains the same from FOO[1] to FOO[2], you can use diamonds instead
of separate statements. For example:
Even better, although less efficient, you can use :Hold to synchronise access to the
variable. For example:
∇ FOO
[1] :Hold 'SEMI_GLOBAL'
[2] :If SEMI_GLOBAL=0
[3] DO_SOMETHING SEMI_GLOBAL
[4] :Else
[5] DO_SOMETHING_ELSE SEMI_GLOBAL
[6] :EndIf
[7] :EndHold
∇
∇ GOO
[1] :Hold 'SEMI_GLOBAL'
[2] SEMI_GLOBAL←1
[3] :EndHold
∇
Now, although you still cannot be sure which of FOO and GOO will run first, you can be
sure that SEMI_GLOBAL will not change (because GOO cuts in) within FOO.
Note that the string used as the argument to :Hold is completely arbitrary, so long as
threads competing for the same resource use the same string.
Warning
If you wish to invoke a niladic function asynchronously, you have the following choices:
∇ NIL_M DUMMY
[1] NIL
∇
NIL_M& ''
⍎& 'NIL'
Note that niladic functions can be invoked asynchronously as callback functions. For
example, the statement:
will execute correctly as a thread, even though NIL is niladic. This is because callback
functions are invoked directly by ⎕DQ rather than as an operand to the spawn operator.
When you define an external function using ⎕NA, you may specify that the function be
run in a separate C thread by appending an ampersand (&) to the function name, for
example:
'beep'⎕NA'user32|MessageBeep& i'
⍝ MessageBeep will run in a separate C thread
When APL first comes to execute a multi-threaded ⎕NA function, it starts a new C-
thread, executes the function within it, and waits for the result. Other APL threads may
then run in parallel.
Note that when the ⎕NA call finishes and returns its result, its new C-thread is retained
to be re-used by any subsequent multithreaded ⎕NA calls made within the same APL
thread. Thus any APL thread that makes any multi-threaded ⎕NA calls maintains a
separate C-thread for their execution. This C-thread is discarded when its APL thread
finishes.
Note that there is no point in specifying a ⎕NA call to be multi-threaded, unless you
wish to execute other APL threads at the same time.
In addition, if your ⎕NA call needs to access an APL GUI object (strictly, a window or
other handle) it should normally run within the same C-thread as APL itself, and not in
a separate C-thread. This is because Windows associates objects with the C-thread that
created them. Although you can use a multi-threaded ⎕NA call to access (say) a Dyalog
APL Form via its window handle, the effects may be different than if the ⎕NA call was
not multi-threaded. In general, ⎕NA calls that access APL (GUI) objects should not be
multi-threaded.
If you wish to run the same ⎕NA call in separate APL threads at the same time, you must
ensure that the DLL is thread-safe. Functions in DLLs which are not thread-safe, must
be prevented from running concurrently by using the :Hold control structure. Note
that all the standard Windows API DLLs are thread safe.
Notice that you may define two separate functions (with different names), one single-
threaded and one multi-threaded, associated with the same function in the DLL. This
allows you to call it in either way.
An application can synchronise its threads by having one thread add tokens into the
pool whilst other threads wait for tokens to become available and retrieve them from
the pool.
The type of a token is a positive or negative numeric scalar. The value of a token is any
arbitrary array that you might wish to associate with it.
The token pool may contain up to 2*31 tokens; they do not have to be unique neither
in terms of their types nor of their values.
The following system functions are used to manage the token pool:
A simple example of a thread synchronisation requirement occurs when you want one
thread to reach a certain point in processing before a second thread can continue.
Perhaps the first thread performs a calculation, and the second thread must wait until
the result is available before it can be used.
This can be achieved by having the first thread put a specific type of token into the pool
using ⎕TPUT. The second thread waits (if necessary) for the new value to be available by
calling ⎕TGET with the same token type.
Notice that when ⎕TGET returns, the specified tokens are removed from the pool.
However, negative token types will satisfy an infinite number of requests for their
positive equivalents.
The system is designed to cater for more complex forms of synchronisation. For
example, a semaphore to control a number of resources can be implemented by
keeping that number of tokens in the pool. Each thread will take a token while
processing, and return it to the pool when it has finished.
A second complex example is that of a latch which holds back a number of threads until
the coast is clear. At a signal from another thread, the latch is opened so that all of the
threads are released. The latch may (or may not) then be closed again to hold up
subsequently arriving threads. A practical example of a latch is a ferry terminal.
For example, if we want to restrict the number of threads that can have sockets open at
any one time.
sock←99 ⍝ socket-token
any +ive number will do).
⎕TPUT 5/sock ⍝ add 5 socket-tokens to pool.
∇ sock_open ...
[1] :If sock=⎕TGET sock ⍝ grab a socket token
[.] ... ⍝ do stuff.
[.] ⎕TPUT sock ⍝ release socket token
[.] :Else
[.] error'sockets off' ⍝ sockets switched off by
retract (see below).
[.] :EndIf
∇
A visual example of a latch might be a ferry terminal, where cars accumulate in the
queue until the ferry arrives. The barrier is then opened and all (up to a maximum
number) of the cars are allowed through it and on to the ferry. When the last car is
through, the barrier is re-closed.
∇ car ...
[1] ⎕TGET tkt ⍝ await ferry.
[2] ...
∇ ferry ...
[1] arrives in port
[2] ⎕TPUT(↑,/⎕TREQ ⎕TNUMS)∩tkt ⍝ ferry tickets for all.
[3] ...
Note that it is easy to modify this example to provide a maximum number of ferry
places per trip by inserting max_places↑ between ⎕TPUT and its argument. If fewer cars
than the ferry capacity are waiting, the ↑ will fill with trailing 0s. This will not cause
problems because zero tokens are ignored.
Let us replace the car ferry with a new road bridge. Once the bridge is ready for traffic,
the barrier could be opened permanently by putting a negative ticket in the pool.
Cars could choose to take the last ferry if there are places:
∇ car ...
[1] :Select ⎕TGET tkt
[2] :Case tkt ⋄ take the last ferry.
[3] :Case -tkt ⋄ ferry full: take the new bridge.
[4] :End
The above :Select works because by default, ⎕TPUT -tkt puts a value of -tkt into the
token.
Using the facilities provided by the Tracer and the Threads Tool (see the Dyalog for
Microsoft Windows UI Guide) it is possible to interrupt (suspend) and restart individual
threads, and to pause and resume individual threads, so any thread may be in one of
three states - running, suspended or paused.
The Tracer and the Session may be connected with any suspended thread and you can
switch the attention of the Session and the Tracer between suspended threads using
)TID or by clicking on the appropriate tab in the Tracer. At this point, you may:
• Examine and modify local variables for the currently suspended thread.
• Trace and edit functions in the current thread.
• Cut back the stack in the currently suspended thread.
• Restart execution.
• Start new threads
The error message from a thread other than the base is prefixed with its thread
number:
260:DOMAIN ERROR
Div[2] rslt←num÷div
^
State indicator displays: )SI and )SINL have been extended to show threads' tree-like
calling structure.
)SI
· #.Calc[1]
&5
· · #.DivSub[1]
· &7
· · #.DivSub[1]
· &6
· #.Div[2]*
&4
#.Sub[3]
#.Main[4]
Here, Main has called Sub, which has spawned threads 4 and 5 with functions: Div and
Calc. Function Div, after spawning DivSub in each of threads 6 and 7, have been
suspended at line [2].
Removing stack frames using Quit from the Tracer or → from the session affects only the
current thread. When the final stack frame in a thread (other than the base thread) is
removed, the thread is expunged.
A paused thread is an inactive thread that is currently being ignored by the thread
scheduler. A paused thread may be paused within a call to ⎕DQ, a call on an external
function, at the beginning of a line, or indeed at any of the thread-switching points
described earlier in this chapter.
A paused thread may be resumed only by the action of a menu item or button. A
paused thread resumes only in the sense that it ceases to be ignored by the thread
scheduler and will therefore be switched back to at some point in the future. It does
not actually continue executing until the switch occurs.
5.2 Triggers
5.2.1 Triggers
Triggers provide the ability to have a function called automatically whenever a variable
or a Field is assigned. Triggers are actioned by all forms of assignment (←), but only by
assignment.
Triggers are designed to allow a class to perform some action when a field is modified –
without having to turn the field into a property and use the property setter function to
achieve this. Avoiding the use of a property allows the full use of the APL language to
manipulate data in a field, without having to copy field data in and out of the class
through get and set functions.
Triggers can also be applied to variables outside a class, and there will be situations
where this is very useful. However, dynamically attaching and detaching a trigger from
a variable is a little tricky at present.
The function that is called when a variable or Field changes is referred to as the Trigger
Function. The name of a variable or Field which has an associated Trigger Function is
termed a Trigger.
Member Description
Name of the Trigger whose change in value has caused the Trigger
Name
Function to be invoked.
NewValue The newly assigned value of the Trigger
The previous value of the Trigger. If the Trigger was not previously
OldValue
defined, a reference to this Field causes a VALUE ERROR .
A Trigger Function is called as soon as possible after the value of a Trigger was assigned;
typically by the end of the currently executing line of APL code. The precise timing is
not guaranteed and may not be consistent because internal workspace management
operations can occur at any time.
If the value of a Trigger is changed more than once by a line of code, the Trigger
Function will be called at least once, but the number of times is not guaranteed.
Expunging a Trigger disconnects the name from the Trigger Function and the Trigger
Function will not be invoked when the Trigger is reassigned. The connection may be re-
established by re-fixing the Trigger Function.
A Trigger may have only a single Trigger Function. If the Trigger is named in more than
one Trigger Function, the Trigger Function that was last fixed will apply.
In general, it is inadvisable for a Trigger function to modify its own Trigger, as this will
potentially cause the Trigger to be invoked repeatedly and forever.
To associate a Trigger function with a local name, it is necessary to dynamically fix the
Trigger function in the function in which the Trigger is localised; for example:
∇ TRIG arg
[1] :Implements Trigger A
[2] ...
∇ TEST;A
[1] ⎕FX ⎕OR'TRIG'
[2] A←10
∇ TRIG arg
[1] :Implements Trigger A,B
[2] [Link]'is now '[Link]
[3] :Trap 6 ⍝ VALUE ERROR
[4] [Link]'was '[Link]
[5] :Else
[6] [Link]' was [undefined]'
[7] :EndTrap
∇
Note that on the very first assignment to A, when the variable was previously
undefined, [Link] is a VALUE ERROR.
A←10
A is now 10
A was [undefined]
A+←10
A is now 20
A was 10
A←'Hello World'
A is now Hello World
A was 20
A[1]←⊂2 3⍴⍳6
A is now 1 2 3 ello World
4 5 6
A was Hello World
B←⌽¨A
B is now 3 2 1 ello World
6 5 4
B was [undefined]
A←⎕NEW MyClass
A is now #.[Instance of MyClass]
A was 1 2 3 ello World
4 5 6
'F'⎕WC'Form'
A←F
A is now #.F
A was #.[Instance of MyClass]
Note that Trigger functions are actioned only by assignment, so changing A to a Form
using ⎕WC does not invoke TRIG.
However, the connection (between A and TRIG) remains and the Trigger Function will
be invoked if and when the Trigger is re-assigned.
A←99
A is now 99
A was #.A
See Section 5.2.4 for information on how a Field (in a Class) may be used as a Trigger.
:Implements Trigger *
Member Description
Name The name of the global variable that is about to be changed.
If the assignment is some form of indexed assignment, Indexers is an
array with the same shape as the sub-array that was assigned and
Indexers
contains the ravel-order, ⎕IO -sensitive, indices of the changed
elements. Otherwise, Indexers is undefined.
Example
∇ foo args
[1] :Implements Trigger *
[2] [Link]'has changed'
[3] :If 2=args.⎕NC'Indexers'
[4] '⍴Indexers'(⍴[Link])
[5] 'Indexers'(,[Link])
[6] :EndIf
∇
vec←⍳5
vec has changed
a b←10 'Pete'
a has changed
b has changed
vec[2 4]←99
vec has changed
⍴Indexers 2
Indexers 2 4
array←2 3 4⍴⍳12
array has changed
(2 1 3↑array)←42
array has changed
⍴Indexers 2 1 3
Indexers 1 2 3 13 14 15
Note
• like other Triggers, only the most recently fixed global trigger function will apply
and be called on assignment to a global variable.
• global triggers do not apply to local names nor to semi-globals (names which
are localised further up the stack).
• an assignment to a global variable will fire both its specific trigger (if defined)
and the global trigger. However, the order of execution is undefined.
• do not use an argument name for your trigger function that may conflict with a
global variable name in the namespace.
Further Example
A potential use for a global trigger is to detect the unintended creation of global
variables due to localisation omissions. Note however that the timing of the activation
of the Trigger is unpredictable. In this example, the trigger for the assignment to b
activates after function hoo has exited. When Threads are involved, timing becomes
even less predictable.
∇ CatchGlobals arg
[1] ⍝ Displays a warning when a global is assigned
[2] :Implements Trigger *
[3] '*** assignment to global variable: ',
[Link],' from ',1↓⎕SI
∇
∇ foo
[1] goo
∇
∇ goo
[1] hoo
∇
∇ hoo
[1] a←10
[2] b←a
∇
foo
*** assignment to global variable: a from hoo goo foo
*** assignment to global variable: b from goo foo
As an example, it is often useful for the Display Form of an Instance to reflect the value
of a certain Field. Naturally, when the Field changes, it is desirable to change the
Display Form. This can be achieved by making the Field a Trigger as illustrated by the
following example.
Notice that the Trigger function is invoked both by assignments made within the Class
(as in the assignment in ctor) and those made from outside the Instance.
:Class MyClass
:Field Public Name
:Field Public Country←'England'
∇ ctor nm
:Access Public
:Implements Constructor
Name←nm
∇
∇ format
:Implements Trigger Name,Country
⎕DF'My name is ',Name,' and I live in ',Country
∇
:EndClass ⍝ MyClass
[Link]←'Greece'
me
My name is Pete and I live in Greece
[Link]←'Kostas'
me
My name is Kostas and I live in Greece
6 APL Files
6.1 Native Files
Introduction
The characteristics of host filesystems vary across different platforms and even across
devices on the same platform. Different host operating systems provide access to their
filesystems in largely incompatible ways. Nevertheless, the native file functions within
Dyalog work in broadly the same way across all platforms, which makes it relatively
straightforward to create applications which are portable across all Dyalog
environments.
Text Files
Characters within a text file may be encoded in one of a number of different ways, and
different host environments tend to have different preferences. Although UTF-8 is
increasingly becoming the de facto encoding standard, other encoding formats exist
(particularly in legacy environments) and there are still multiple conventions for
representing line endings.
Dyalog includes two powerful functions – Dyalog APL Language: Nget and Dyalog APL
Language: Nput – which read and write text files to or from character arrays in the
workspace. The encoding and line-ending types can be explicitly specified but by
default ⎕NGET will try to deduce the encoding automatically and ⎕NPUT will use defaults
appropriate for the host environment.
The search and replace functions Dyalog APL Language: S and Dyalog APL Language: R
can also be used to read and write text files, filtering and modifying the content as they
do so.
The simplest text files contain just plain text – variable length lines of text with no
formatting such as italics etc. However, formatting or data encoding can be included
within a text file using formats such markup (e.g. HTML and XML), character separated
values (CSV) or JavaScript Object Notation (JSON). Dyalog includes the functions Dyalog
APL Language: Xml, Dyalog APL Language: Csv and Dyalog APL Language: Json to
decode or encode such file content.
Binary data files contain data in application-specific format, and are rarely read or
written by anything other than the application which creates them and understands
their format. Dyalog provides a number of functions which allow a Dyalog application
to manage its own binary data files or binary data files from any source, by allowing
them to be read or written as sequences of bytes or words. Regions of binary files can
also be locked to coordinate shared access to the files.
Files are tied (opened) using Dyalog APL Language: Ntie or created using Dyalog APL
Language: Ncreate. Both of these functions return a numeric tie number by which the
file is subsequently identified when read (Dyalog APL Language: Nread), written
(Dyalog APL Language: Nappend, Dyalog APL Language: Nreplace), renamed (Dyalog
APL Language: Nrename) locked or unlocked (Dyalog APL Language: Nlock) or (re)sized
(Dyalog APL Language: Nsize, Dyalog APL Language: Nresize). When a file tie is no
longer needed it can be untied (closed) using Dyalog APL Language: Nuntie or closed
and the file deleted using Dyalog APL Language: Nerase. Dyalog APL Language: Nnums
and Dyalog APL Language: Nnames report the numbers and names respectively of all
currently tied files.
Filesystem Manipulation
In addition to reading and writing files, an application may typically want to manipulate
the filesystem in various ways. A diverse set of functions exist to do this: Dyalog APL
Language: Mkdir creates directories, Dyalog APL Language: Ndelete deletes files and/
or directories and their contents, Dyalog APL Language: Nmove and Dyalog APL
Language: Ncopy move and copy files and/or directories and their contents. Dyalog
APL Language: Ninfo queries and sets properties such as size or modification date on
files and directories, and can also query the contents of directories. Dyalog APL
Language: Nexists will report whether or not a name exists within the file system.
Additionally, 739⌶ can be used to obtain the name of the directory used by the host
filesystem for temporary files (generally emptied at startup).
Progress Callbacks
The native file functions ⎕NCOPY, ⎕NMOVE, and ⎕NINFO support the ProgressCallback
variant option to enable progress callbacks.
Overview
If this option is enabled, the system function invokes an APL callback function at several
stages as a file operation proceeds, meaning that the results of the system function can
be accessed as they become available rather than waiting for them to all be available. A
system object is used to communicate between the system function and the callback.
1. The start of the operation. The callback is invoked before any directories are
scanned or files are processed. This gives the application the opportunity to set
parameters that control the frequency of callbacks during the operation itself.
2. The optional scan phase during which the system function enumerates the files
that will be involved in the copy or move operation. The file count obtained is
used to set the Limit field. The application may use this subsequently to
indicate the degree of progress.
3. The main processing of the files.
4. The end of the operation.
The callback function is invoked once at the start of the operation, during the (optional)
scan and processing stages, and finally once at the end of the operation. During the
scan and processing stages, the Skip and Delay options provide alternative ways to
control the frequency with which the function is invoked.
If both options are 0, the callback will be invoked after every file is processed. However,
if there are a large number of small files involved, and you simply want to update a
progress bar, this may prove to be unnecessarily frequent, and will increase the total
time required to complete the operation.
If you want to update a progress bar regularly (for example every second), the Delay
option (1000 = 1 second) is the better choice. In other circumstances, you might choose
to use Skip.
If you use both options, the callback will be invoked when both apply, so if you set Skip
to 10 and Delay to 5000, the callback will be invoked after at least 10 files have been
processed and at least 5 seconds have elapsed since the previous invocation of the
callback.
Event
Event is a character vector which indicates the stage of the copy or move operation.
Note that there will always be at least 2 invocations of the callback, to indicate the start
and end of the operation.
Info
Info is a reference to a namespace that contains information about the event. This
namespace persists for the duration of the execution of the system function and
contains the following fields:
Options
This Namespace contains options that control future invocations of the callback. The
options persist between these invocations, so there is no need to set them again unless
they should be changed. The fields and their default values are:
The result of the callback function is a Boolean scalar indicating whether the system
function should continue or stop:
6.2.1 Introduction
Most languages store programs and data separately. APL is unusual in that it allows you
to store programs and data together in a workspace.
This can be inefficient if your dataset gets very large; when your workspace is loaded,
you are loading ALL of your data, whether you need it or not.
It also makes it difficult for other users to access your data, particularly if you want
them to be able to update it.
In these circumstances, you must extract your data from your workspace, and write it
to a file on disk, thus separating your data from your program. There are many different
kinds of file format. This section is concerned with the APL Component File system
which preserves the idea that your data consists of APL objects; hence you can only
access this type of file from within APL
The Component File system has a set of system functions through which you access the
file. Although this means that you have to learn a whole new set of functions in order
to use files, you will find that they provide you with a very powerful mechanism to
control access to your data.
Overview
A component file is a data file maintained by Dyalog APL. It contains a series of APL
arrays known as components which are accessed by reference to their relative position
or component number within the file. Component files are just like other data files and
there are no special restrictions imposed on names or sizes.
To access an existing component file it must be tied, that is, opened for use. The tie
may be exclusive (single-user access) or shared (multi-user access). A file is untied, that
is, closed, using ⎕FUNTIE or on terminating Dyalog APL. File ties survive )LOAD, ⎕LOAD
and )CLEAR operations.
Tie Numbers
A file is tied by associating a file name with a tie number. Tie numbers are integers in
the range 1 - 2147483647 and, you can supply one explicitly, or have the interpreter
allocate the next available one by specifying 0. The system functions which tie files
return the tie number as a "shy" result.
A component file is created using ⎕FCREATE which automatically ties the file for
exclusive use. A newly created file is empty, that is, contains 0 components. A file is
removed with ⎕FERASE, although it must be exclusively tied to do so.
Components are added to a file using ⎕FAPPEND and removed using ⎕FDROP.
Component numbers are allocated consecutively starting at 1. Thus a new component
added by ⎕FAPPEND is given a component number which is one greater than that of the
last component in the file. Components may be removed from the beginning or end of
the file, but not from the middle. Component numbers are therefore contiguous.
Components are read using ⎕FREAD and overwritten using ⎕FREPLACE. There are no
restrictions on the size or type of array which may replace an existing component.
Components are accessed by component number.
Component Information
In addition to the data held in a component, the user ID that wrote it and the time at
which it was written is also recorded.
Multi-User Access
⎕FSTIE ties a file for shared (that is, multi-user) access. This kind of access would be
appropriate for a multi-user UNIX system, a network of single user PCs, or multiple APL
tasks under Microsoft Windows.
⎕FHOLD provides the means for the user to temporarily prevent other co-operating
users from accessing one or more files. This is necessary to allow a single logical update
involving more than one component, and perhaps more than one file, to be completed
without interference from another user. ⎕FHOLD is applicable to External Variables as
well as Component Files
There are two levels of file access control. As a regular file, the operating system read/
write controls for owner and other users apply. In addition, Dyalog manages its own
access controls using the access matrix. This is an integer matrix with 3 columns and
any number of rows. Column 1 contains user numbers, column 2 an encoding of
permitted file operations, and column 3 passnumbers. Each row specifies which file
operations may be performed by which user(s) with which passnumber. A value of 0 in
column 1 specifies all users. A value of ¯1 in column 2 specifies all file operations. A
value of 0 in column 3 specifies no passnumber. If any row of the access matrix contains
(0 ¯1 0) it specifies that all users may perform all file operations with no passnumber.
User Number
Under Windows, this is a number which is defined by the aplnid parameter. If you
intend to use Dyalog's access matrix to control file access in a multi-user environment,
it is desirable to allocate to each user, a distinct user number. However, if you intend to
rely on underlying operating system controls, allocating a user number of 0 (the default
installation value) to everyone is more appropriate. Under non-Windows platforms the
User Number is set to be the effective user-id of the APL process and cannot be altered.
In both cases, a user number of 0 causes APL to circumvent the access matrix
mechanism described below.
Permission Code
This is an integer representation of a Boolean mask. Each bit in the mask indicates
whether or not a particular file operation is permitted as follows:
For example, if bits 1, 4 and 6 are set and all other relevant bits are zero only ⎕FREAD,
⎕FAPPEND and ⎕FDROP are permitted. A convenient way to set up the mask is to sum the
access codes associated with each operation.
For example, the value 41 (1+8+32) authorises ⎕FREAD, ⎕FAPPEND and ⎕FDROP. A value
of ¯1 (all bits set) permits all operations. Thus by subtracting the access codes of
operations to be forbidden, it is possible to permit all but certain types of access. For
example, a value of ¯133 (¯1- 4+128) permits all operations except ⎕FERASE and
⎕FRENAME. Note that the value of unused bits is ignored. Any non-zero permission code
allows ⎕FSTIE and ⎕FSIZE. ⎕FCREATE, ⎕FUNTIE, ⎕FLIB, ⎕FNAMES and ⎕FNUMS are not
subject to access control. Passnumbers may also be used to establish different levels of
access for the same user.
When the user attempts to tie a file using ⎕FTIE or ⎕FSTIE a row of the access matrix is
selected to control this and subsequent operations.
If the user is the owner, and the owner's user ID does not appear in the access matrix,
the value (⎕AI[1] ¯1 0) is conceptually appended to the access matrix. This ensures
that the owner has full access rights unless they are explicitly restricted.
The chosen row is the first row in which the value in column 1 of the access matrix
matches the user ID and the value in column 3 matches the supplied passnumber
which is taken to be zero if omitted.
If there is no match of user ID and passnumber in the access matrix (including implicitly
added rows) then no access is granted and the tie fails with a FILE ACCESS ERROR.
Once the applicable row of the access matrix is selected, it is used to verify all
subsequent file operations. The passnumber used to tie the file MUST be used for
every subsequent operation. Secondly, the appropriate bit in the permission code
corresponding to the file operation in question must be set. If either of these
conditions is broken, the operation will fail with FILE ACCESS ERROR.
If the access matrix is changed while a user has the file tied, the change takes
immediate effect. When the user next attempts to access the file, the applicable row in
the access matrix will be reselected subject to the supplied passnumber being the
same as that used to tie the file. If access with that password is rescinded the operation
will fail with FILE ACCESS ERROR.
When a file is created using ⎕FCREATE, the access matrix is empty. At this stage, the
owner has full access with passnumber 0, but no access with a non-zero passnumber.
Other users have no access permissions. Thus only the owner may initialise the access
matrix.
User 0
If a user has an aplnid of 0, the access matrix and supplied passnumbers are ignored.
This user is granted full and unrestricted access rights to all component files, subject
only to underlying operating system restrictions.
⎕FLIB gives a list of component files in a given directory. ⎕FNAMES and ⎕FNUMS give a list
of the names and tie numbers of tied files. These general operations which apply to
more than one file are not subject to access controls.
See Language Reference for full details of the syntax of these system functions.
General
⎕FAVAIL Report file system availability
File Operations
⎕FCREATE Create a file
⎕FTIE Tie an existing file (exclusive)
⎕FSTIE Tie an existing file (shared)
⎕FUNTIE Untie file(s)
⎕FCOPY Copy a file
⎕FERASE Erase a file
⎕FRENAME Rename a file
File information
⎕FHIST Report file events
⎕FNUMS Report tie numbers of tied files
⎕FNAMES Report names of tied files
⎕FLIB Report names of component files
⎕FPROPS Report file properties
⎕FSIZE Report size of file
Writing to the file
⎕FAPPEND Append a component to the file
⎕FREPLACE Replace an existing component
Reading from a file
⎕FREAD Read one or more components
⎕FRDCI Read component information
Manipulating a file
⎕FDROP Drop a block of components
⎕FRESIZE Change file size (forces a compaction)
⎕FCHK Check and repair a file
Access manipulation
Let us suppose that you have written an APL system that builds a personnel database,
containing the name, age and place of birth of each employee. Let us assume that you
have created a variable DATA, which is a nested vector with each element containing a
person's name, age and place of birth:
DISPLAY 2↑DATA
.→-------------------------------------------------------.
| .→----------------------. .→-------------------------. |
| | .→-------. .→----. | | .→------. .→--------. | |
| | |Jonathan| 42 |Wales| | | |Pauline| 21 |Isleworth| | |
| | '--------' '-----' | | '-------' '---------' | |
| '∊----------------------' '∊-------------------------' |
'∊-------------------------------------------------------'
Then the following APL expressions can be used to access the database:
Example 1
Show record 2
DISPLAY 2⊃DATA
.→-------------------------.
| .→------. .→--------. |
| |Pauline| 21 |Isleworth| |
| '-------' '---------' |
'∊-------------------------'
Example 2
⍴DATA
123
Example 3
(2 2⊃DATA)←16
Example 4
Create a new file, giving the file name, and the number you wish to use to identify it
(the file tie number):
'COMPFILE' ⎕FCREATE 1
If the file already exists, or you have already used this tie number, then APL will
respond with the appropriate error message.
Now write the data to the file. We could write a function that loops to do this, but it is
neater to take advantage of the fact that our data is a nested vector, and use each (¨).
DATA ⎕FAPPEND¨ 1
Example 1
Show record 2
DISPLAY ⎕FREAD 1 2
.→-------------------------.
| .→------. .→--------. |
| |Pauline| 21 |Isleworth| |
| '-------' '---------' |
'∊-------------------------'
Example 2
The fourth element of ⎕FSIZE indicates the file size limit. Dyalog APL does not impose a
file size limit, although your operating system may do so, but the concept is retained in
order to make this version of Component Files compatible with others.
Example 3
Example 4
Example 5
'PERSONNEL' ⎕FRENAME 1
Example 6
Tie an existing file; give file name and have the interpreter allocate the next available
tie number.
'SALARIES' ⎕FTIE 0
2
Example 7
(1 3⍴0 ¯1 0)⎕FSTAC 1
Example 8
Example 9
⎕FNAMES,⎕FNUMS
PERSONNEL 1
SALARIES 2
Example 10
⎕FUNTIE ⎕FNUMS
Obviously, Dyalog APL contains mechanisms that prevent data getting mixed up if two
users update a file at the same time. However, it is the programmer's responsibility to
control the logic of multi-user updates.
For example, suppose two people are updating our database at the same time. The first
checks to see if there is an entry for 'Geoff', sees that there isn't so adds a new
record. Meanwhile, the second user is checking for the same thing, and so also adds a
record for 'Geoff'. Each user would be running code similar to that shown below:
∇ UPDATE;DATA;NAMES
[1] ⍝ Using the component file
[2] 'PERSONNEL' ⎕FSTIE 1
[3] NAMES←⊃∘⎕FREAD ¨ 1,¨⍳¯1+2⊃⎕FSIZE 1
[4] →END×⍳(⊂'Geoff')∊NAMES
[5] ('Geoff' 41 'Hounslow')⎕FAPPEND 1
[6] END:⎕FUNTIE 1
∇
The system function ⎕FHOLD provides the means for the user to temporarily prevent
other co-operating users from accessing one or more files. This is necessary to allow a
single logical update, perhaps involving more than one record or more than one file, to
be completed without interference from another user.
∇ UPDATE;DATA;NAMES
[1] ⍝ Using the component file
[2] 'PERSONNEL' ⎕FSTIE 1
[3] ⎕FHOLD 1
[4] NAMES←⊃∘⎕FREAD ¨ 1,¨⍳¯1+2⊃⎕FSIZE 1
[5] →END×⍳(⊂'Geoff')∊NAMES
[6] ('Geoff' 41 'Hounslow')⎕FAPPEND 1
[7] END:⎕FUNTIE 1 ⋄ ⎕FHOLD ⍳0
∇
Successive ⎕FHOLDs on a file executed by different users are queued by Dyalog APL;
once the first ⎕FHOLD is released, the next on the queue holds the file. ⎕FHOLDs are
released by return to immediate execution, by ⎕FHOLD ⍬, or by erasing the external
variable.
It is easy to misunderstand the effect of ⎕FHOLD. It is NOT a file locking mechanism that
prevents other users from accessing the file. It only works if the tasks that wish to
access the file co-operate by queuing for access by issuing ⎕FHOLDs. It would be very
inefficient to issue a ⎕FHOLD on a file then allow the user to interactively edit the data
with the hold in operation. What happens if he goes to lunch? Any other user who
wants to access the file and cooperates by issuing a ⎕FHOLD would have to wait in the
queue for 3 hours until the first user returns, finishes his update and his ⎕FHOLD is
released. It is usually more efficient (as well as more friendly) to issue ⎕FHOLDs around a
small piece of critical code.
Suppose we had a control file associated with our personnel data base. This control file
could be an external variable, or a component file. In both cases, the concept is the
same; only the commands needed to access the file are different. In this example, we
will use a component file:
Now we'll allow our man that likes long lunch breaks to edit the file, but will control the
hold in a more efficient way:
∇ EDIT;CMP;CV
[1] ⍝ Share-tie the control file
[2] 'CONTROL' ⎕FSTIE 1
[3] ⍝ Share-tie the data file
[4] 'PERSONNEL' ⎕FSTIE 2
[5] ⍝ Find out which component the user wants to edit
[6] ASK:CMP←ASK∆WHICH∆RECORD
[7] ⍝ Hold the control file
[8] ⎕FHOLD 1
[9] ⍝ Read the control vector
[10] CV←⎕FREAD 1 1
[11] ⍝ Make control vector as big as the data file
[12] CV←(¯1+2⊃⎕FSIZE 2)↑CV
[13] ⍝ Look at flag for this component
[14] →(FREE,INUSE)[1+CMP⊃CV]
[15] ⍝ In use - tell user and release hold
[16] INUSE:'Record in use' ⋄ ⎕FHOLD ⍬ ⋄ →ASK
[17] ⍝ Ok to use - flag in-use and release hold
[18] FREE:CV[CMP]←1 ⋄ CV ⎕FREPLACE 1 1⋄ ⎕FHOLD ⍬
[19] ⍝ Let user edit the record
[20] EDIT∆RECORD RECORD
[21] ⍝ When he's finished, clear the control vector
[22] ⎕FHOLD 1
[23] CV←⎕FREAD 1 1 ⋄CV[CMP]←0 ⋄ CV ⎕FREPLACE 1 1
[26] ⎕FHOLD ⍬
[27] ⍝ And repeat
[28] →ASK
∇
Component 1 of our CONTROL file acts as a control vector. Its length is set equal to the
number of components in the PERSONNEL file, and an element is set to 1 if a user
wishes to access the corresponding data component. Only the control file is ever
subject to a ⎕FHOLD, and then only for a split-second, with no user inter-action being
performed whilst the hold is active.
When the first user runs the function, the relevant entry in the control vector will be
set to 1. If a second user accesses the database at the same time, he will have to wait
briefly whilst the control vector is updated. If he wants the same component as the first
user, he will be told that it is in use, and will be given the opportunity to edit something
else.
This simple mechanism allows us to lock the components of our file, rather the than
entire file. You can set up more informative control vectors than the one above; for
example, you could easily put the user name into the control vector and this would
enable you to tell the next user who is editing the component he is interested in.
There are many different ways to structure data files; you must design a structure that
is the most efficient for your application.
The internal structure of external variables and component files is the same, and the
examples given below apply to both.
'TEMP' ⎕FCREATE 1
'One' 'Two' 'Three' ⎕FAPPEND¨1
Dyalog APL will write these components onto contiguous areas of disk:
'Six' ⎕FREPLACE 1 2
If your system uses fixed length records, then the size of your components never
change, and the internal structure of the file remains static.
This will not fit into the area currently assigned to component 1, so it is appended to
the end of the file. Dyalog APL maintains internal tables which contain the location of
each component; hence, even though the components may not be physically stored in
order, they can always be accessed in order.
'BigThree' ⎕FREPLACE 1 3
Component 3 is appended to the end of the file, and the area that was used before
becomes free:
Dyalog APL keeps tables of the size and location of the free areas, as well as the actual
location of your data. Now we'll replace component 2 with something bigger:
'BigTwo' ⎕FREPLACE 1 2
Free areas are used whenever possible, and contiguous holes are amalgamated.
You can see that if you are continually updating your file with larger data objects, then
the file structure can become fragmented. At any one time, the disk area occupied by
your file will be greater than the area necessary to hold your data. However, free areas
are constantly being reused, so that the amount of unused space in the file will seldom
exceed 30%.
Whenever you issue a monadic ⎕FRESIZE command on a component file, Dyalog APL
COMPACTS the file; that is, it restructures it by reordering the components and by
amalgamating the free areas at the end of the file. It then truncates the file and
releases the disk space back to the operating system (note that some versions of UNIX
do not allow the space to be released). For a large file with many components, this
process may take a significant time.
Error Conditions
A FILE SYSTEM NOT AVAILABLE (Error code 28) error will be generated if the operating
system returns an unexpected error when attempting to get a lock on a component file.
In Windows environments this may indicate that opportunistic locks (aka oplocks) are in
use; they should be disabled if Dyalog components files are being used.
A FILE SYSTEM TIES USED UP (Error code 30) error will be generated when an
attempt is made to open more component files than is possible.
FILE TIED
A FILE TIED error is reported if you attempt to tie a file which another user has
exclusively tied.
Limitations
The File Tie Quota is the maximum number of files that a user may tie concurrently.
Dyalog APL itself allows a maximum of 1024 under UNIX and 512 under Windows,
although in either case your installation may impose a lower limit. When an attempt is
made to exceed this limit, the report FILE TIE QUOTA (Error code 31) is given. This
error will also be generated if an attempt is made to exceed the maximum number of
open files that is imposed by the operating system.
Dyalog APL records the names of each user's tied files in a buffer of 40960 bytes. When
this buffer is full, the report FILE NAME QUOTA USED UP (Error code 32) will be given.
This is only likely to occur if long pathnames are used to identify files.
.------------------.
| Operating System | .--------. .---------.
| instruction to |-->| BUFFER |--->| File on |
| write large data | ---------. | disk |
| object to a file | ----------.
-------------------.
When you issue a write to a disk area, the data is not necessarily sent straight to the
disk. Sometimes it is written to an internal buffer (or cache), which is usually held in
(fast) main memory. When the buffer is full, the contents are passed to the disk. This
means that at any one time, you could have data in the buffer, as well as on the disk. If
your machine goes down whilst in this state, you could have a partially updated file on
the disk. In these circumstances, the operating system generally recovers your file
automatically.
If this facility is exploited, it offers very fast file updating. For systems that are I/O
bound, this is a very important consideration. However, the disadvantage is that whilst
it may appear that a write operation has completed successfully, part of the data may
still be residing in the buffer, waiting to be flushed out to the disk. It is usually possible
to force the buffer to empty; see your operating system manuals for details (UNIX
automatically invokes the sync() command every few seconds to flush its internal
buffers).
Dyalog APL exploits this facility, employing buffers internal to APL as well as making use
of the system buffers. Of course, these techniques cannot be used when the file is
shared with other users; obviously, the updates must be written immediately to the
disk. However, if the file is exclusively tied, then several layers of buffers are employed
to ensure that file access is as fast as possible.
You can ensure that the contents of all internal buffers are flushed to disk by issuing
⎕FUNTIE ⍬ at any time.
features. These are optional because the additional security these features provide
comes at the cost of reduced performance. You can choose the level of security that is
appropriate for your application.
When journaling is enabled (see ⎕FPROPS), files are updated using a journal which
effectively prevents system or network failures from causing file damage.
Additional security is provided by the check sum facility which enables component files
to be repaired using the system function ⎕FCHK. See File Check and Repair.
Level 2 journaling provides protection not just against the possibility that the APL
process terminates abnormally, but that the Operating System itself fails. However, a
damaged component file must be explicitly repaired using the system function ⎕FCHK
which will repair any damaged components by rolling them back to their previous
states.
Level 3 provides the same level of protection as Level 2, but following the abnormal
termination of either APL or the Operating System, the rollback of an incomplete
update will be automatic and no explicit repair will be needed.
APL files are treated as normal data files by the operating system, and may be
manipulated by any of the standard operating system commands.
Do not use operating system commands to copy, erase or move component files that
are tied and in use by an APL session.
Information
Component files that have both journalling and checksum properties set to 0
have been deprecated; from Dyalog v21.0, component files with this combination
of properties will be read-only. Dyalog Ltd recommends using ⎕FPROPS to
convert any such files to have different properties. For information on how to
identify component files that have both journalling and checksum properties set
to 0 in your existing codebase, see the Release Notes.
7 Error Trapping
7.1 Standard Error Action
The standard system action in the event of an error or interrupt whilst executing an
expression is to suspend execution and display an error report. If necessary, the state
indicator is cut back to a statement such that there is no halted locked function visible
in the state indicator.
1. The error message, preceded by the symbol ⍎ if the error occurred while
evaluating the Execute function.
2. The statement in which the error occurred (or expression being evaluated by the
Execute function), preceded by the name of the function and line number where
execution is suspended unless the state indicator has been cut back to
immediate execution mode. If the state indicator has been cut back because of a
locked function in execution, the displayed statement is that from which the
locked function was invoked.
3. The symbol ^ under the last referenced symbol or name when the error
occurred. All code to the right of the ^ symbol in the expression will have been
evaluated.
Examples
X PLUS U
VALUE ERROR
X PLUS U
^
FOO
INDEX ERROR
FOO[2] X←X+A[I]
^
CALC
⍎DOMAIN ERROR
CALC[5] ÷0
^
First, we must have an idea of what is meant by error trapping. We are all used to
entering some duff APL code, and seeing a (sometimes) rather obscure, esoteric error
message echoed back:
10÷0
DOMAIN ERROR
10÷0
^
This message is ideal for the APL programmer, but not so for the end user. We need a
way to bypass the default action of APL, so that we can take an action of our own,
thereby offering the end user a more meaningful message.
Every error message reported by Dyalog APL has a corresponding error number (for a
list of error codes and message, see ⎕TRAP, Language Reference). Many of these error
numbers plus messages are common across all versions of APL. We can see that the
code for DOMAIN ERROR is 11, whilst LENGTH ERROR has code 5.
Dyalog APL provides two distinct but related mechanisms for the trapping and control
of errors. The first is based on the control structure :Trap ... :EndTrap, and the
second, on the system variable ⎕TRAP. The control structure is easier to administer and
so is recommended for normal use, while the system variable provides slightly finer
control and may be necessary for specialist applications.
Dyalog APL keeps a note of the last error that occurred, and provides this information
through system functions: ⎕EN, ⎕EM and ⎕DM.
10÷0
DOMAIN ERROR
10÷0
^
⎕EN
11
⎕EM 11
DOMAIN ERROR
⎕DM
DOMAIN ERROR 10÷0 ^
DISPLAY ⎕DM
┌→─────────────────────────────────────┐
│ ┌→───────────┐ ┌→─────────┐ ┌→─────┐ │
│ │DOMAIN ERROR│ │ 10÷0│ │ ∧│ │
│ └────────────┘ └──────────┘ └──────┘ │
└∊─────────────────────────────────────┘
Mix (↑) of this vector produces a matrix that displays the same as the error message
produced by APL:
↑⎕DM
DOMAIN ERROR
10÷0
^
You can embed a number of lines of code in a :Trap control structure within a defined
function.
[1] ...
[2] :Trap 0
[3] ...
[4] ...
[5] :EndTrap
[6] ...
Now, whenever any error occurs in one of the enclosed lines, or in a function called
from one of the lines, processing stops immediately and control is transferred to the
line following the :EndTrap. The 0 argument to :Trap, in this case represents any error.
To trap only specific errors, you could use a vector of error numbers:
[2] :Trap 11 2 3
Notice that in this case, no extra lines are executed after an error. Control is passed to
line [6] either when an error has occurred, or if all the lines have been executed
without error. If you want to execute some code only after an error, you could re-code
the example like this:
[1] ...
[2] :Trap 0
[3] ...
[4] ...
[5] :Else
[6] ...
[7] ...
[8] :EndTrap
[9] ...
Now, if an error occurs in lines [3-4], (or in a function called from those lines), control
will be passed immediately to the line following the :Else statement. On the other
hand, if all the lines between :Trap and :Else complete successfully, control will pass
out of the control structure to (in this case) line [9].
The final refinement is that specific error cases can be accommodated using
:Case[List] constructs in the same manner as the :Select control structure.
Note that :Trap can be used in conjunction with ⎕SIGNAL described below.
Traps can be nested. In the following example, code in the inner trap structure
attempts to tie a component file, and if unsuccessful, tries to create one. In either case,
the tie number is then passed to function ProcessFile. If an error other than 22 (FILE
NAME ERROR) occurs in the inner trap structure, or an error occurs in function
ProcessFile (or any of its called function), control passes to line immediately to line
[9].
[1] :Trap 0
[2] :Trap 22
[3] tie←name ⎕FTIE 0
[4] :Else
[5] tie←name ⎕FCREATE 0
[6] :EndTrap
[7] ProcessFile tie
[8] :Else
[9] 'Unexpected Error'
[10] :EndTrap
The second way of trapping errors is to use the system variable: ⎕TRAP.
⎕TRAP, can be assigned a nested vector of trap specifications. Each trap specification is
itself a nested vector, of length 3, with each element defined as:
list of error
The error numbers we are interested in.
numbers
Either 'E' (Execute) or 'C' (Cut Back). There are others, but
action code
they are seldom used.
action to be APL expression, usually a branch statement or a call to an APL
taken function.
The action code E tells APL that you want your action to be taken in the function in
which the error occurred, whereas the code C indicates that you want your action to be
taken in the function where the ⎕TRAP was localised. If necessary, APL must first travel
back up the state indicator (cut-back) until it reaches that function.
When we enter:
10÷0
APL executes the expression, and notes that it causes an error number 11. Before
issuing the standard error, it scans its ⎕TRAP table, to see if you were interested enough
in that error to set a trap; you were, so APL executes the action specified by you:
10÷0
Please give non-zero right arg
and write a defined function to take the place of the primitive function ÷:
∇ R←A DIV B
[1] R←A÷B
[2] ∇
10 DIV 0
DOMAIN ERROR
DIV[1] R←A÷B
^
Running the function with good and bad arguments has the desired effect:
10 DIV 2
5
10 DIV 0
Please give a non-zero right arg
⎕TRAP is a variable like any other, and since it is localised in DIV, it is only effective in
DIV and any other functions that may be called by DIV. So
10÷0
DOMAIN ERROR
10÷0
^
still gives an error, since there is no trap set in the global environment.
Other Errors
1 2 3 DIV 4 5
LENGTH ERROR
DIV [4] R←A÷B
^
)RESET
1 2 3 DIV 4 5
Arguments must be the same length
Global Traps
Often when we are writing a system, we can't think of everything that may go wrong
ahead of time; so we need a way of catching "everything else that I may not have
thought of". The error number used for "everything else" is zero:
)RESET
In this case, when APL executed line 4 of our function DIV, it encountered an error
number 4 (RANK ERROR). It searched the local trap table, found nothing relating to error
4, so searched further up the stack to see if the error was mentioned anywhere else. It
found an entry with an associated Execute code, so executed the appropriate action AT
THE POINT THAT THE ERROR OCCURRED. Let's see what's in the stack:
)SI
DIV[4]*
↑⎕DM
RANK ERROR
DIV[4] R←A÷B
^
So although our action has been taken, execution has stopped where it normally would
after a RANK ERROR.
Dangers
We must be careful when we set global traps; let's call the non-existent function BUG
whenever we get an unexpected error:
)RESET
⎕TRAP ← 0 'E' 'BUG'
(2 3⍴⍳6) DIV (2 3 4⍴⍳24)
Nothing happens, since APL traps a RANK ERROR on line 4 of DIV, so executes the trap
statement, which causes a VALUE ERROR, which activates the trap action, which causes
a VALUE ERROR, which .... etc. etc. If we had also chosen to trap on 1000 (ALL
INTERRUPTS), then we'd be in trouble!
∇ BUG
[1] ⍝ Called whenever there is an unexpected error
[2] '*** UNEXPECTED ERROR OCCURRED IN: ',⊃1↓⎕SI
[3] '*** PLEASE CALL YOUR SYSTEM ADMINISTRATOR'
[4] '*** WORKSPACE SAVED AS BUG.',⊃1↓⎕SI
[5] ⍝ Tidy up ... reset ⎕LX, untie files ... etc
[6] ⎕SAVE 'BUG.',⊃1↓⎕SI
[7] '*** LOGGING YOU OFF THE SYSTEM'
[8] ⎕OFF
∇
Now, whenever we run our system and an unexpected error occurs, our BUG function
will be called.
10 DIV 0
Please give non-zero right arg
The system administrator can then load [Link], look at the SI stack, discover the
problem, and fix it.
In many cases, you can of course achieve the same effect of a trap by using APL code to
detect the problem before it happens. Consider the function TIE∆FILE, which checks
to see if a file already exists before it tries to access it:
∇ R←TIE∆FILE FILE;FILES
[1] ⍝ Tie file FILE with next available tie number
[2] ⍝
[3] ⍝ All files in my directory
[4] FILES←⎕FLIB 'mydir'
[5] ⍝ Remove trailing blanks
[6] FILES←dbr¨↓FILES
[7] ⍝ Required file in list?
[8] →ERR×⍳~(⊂FILE)∊FILES
[9] ⍝ Tie file with next number
[10] FILE ⎕FTIE R←1+⌈/0,⎕FNUMS
[11] ⍝ ... and exit
[12] →0
[13] ⍝ Error message
[14] ERR:R←'File does not exist'
∇
This function executes the same code whether the file name is right or wrong, and it
could take a while to get all the file names in your directory. It would be neater, and
more efficient to take action ONLY when the file name is wrong:
∇ R←TIE∆FILE FILE;⎕TRAP
[1] ⍝ Tie file FILE with next available tie number
[2] ⍝
[3] ⍝ Set trap
[4] ⎕TRAP←22 'E' '→ERR'
[5] ⍝ Tie file with next number
[6] FILE ⎕FTIE R←1+⌈/0,⎕FNUMS
[7] ⍝ ... and exit if OK
[8] →0
[9] ⍝ Error message
[10] ERR:R←'File does not exist'
Let us consider the effect of using Cut-Back instead of Execute. Consider the system
illustrated below, in which the function REPORT gives the user the option of 4 reports to
be generated:
REPORT
|
.-------------------------.
| | | |
REP1 REP2 REP3 REP4
|
.----.----.
| | |
... DIV ...
∇ REPORT;OPTIONS;OPTION;⎕TRAP
[1] ⍝ Driver functions for report sub-system. If an
[2] ⍝ unexpected error occurs, take action in the
[3] ⍝ function where the error occurred
[4] ⍝
[5] ⍝ Set global trap
[6] ⎕TRAP←0 'E' 'BUG'
[7] ⍝ Available options
[8] OPTIONS←'REP1' 'REP2' 'REP3' 'REP4'
[9] ⍝ Ask user to choose
[10] LOOP:→END×⍳0=⍴OPTION←MENU OPTIONS
[11] ⍝ Execute relevant function
[12] ⍎OPTION
[13] ⍝ Repeat until EXIT
[14] →LOOP
[15] ⍝ Now end
[16] END:
Suppose the user chooses REP3, and an unexpected error occurs in DIV. The good news
is that the System Administrator gets a snapshot copy of the workspace that he can
play about with:
The bad news is, our user is locked out of the whole system, even though it may only
be REP3 that has a problem. We can get around this by making use of the CUT-BACK
action code.
∇ REPORT;OPTIONS;OPTION;⎕TRAP
[1] ⍝ Driver functions for report sub-system. If an
[2] ⍝ unexpected error occurs, cut the stack back
[3] ⍝ to this function, then take action
[4] ⍝
[5] ⍝ Set global trap
[6] ⎕TRAP←0 'C' '→ERR'
[7] ⍝ Available options
[8] OPTIONS←'REP1' 'REP2' 'REP3' 'REP4'
[9] ⍝ Ask user to choose
[10] LOOP:→END×⍳0=⍴OPTION←MENU OPTIONS
[11] ⍝ Execute relevant function
[12] ⍎OPTION
[13] ⍝ Repeat until EXIT
[14] →LOOP
[15] ⍝ Tell user ...
[16] ERR:MESSAGE'Unexpected error in',OPTION
[17] ⍝ ... what's happening
[18] MESSAGE'Removing from list'
[19] ⍝ Remove option from list
[20] OPTIONS←OPTIONS~⊂OPTION
[21] ⍝ And repeat
[22] →LOOP
[23] ⍝ End
[24] END:
Suppose the user runs this version of REPORT and chooses REP3. When the unexpected
error occurs in DIV, APL will check its trap specifications, and see that the relevant trap
was set in REPORT with a cut-back code. APL therefore cuts back the stack to the
function in which the trap was localised, THEN takes the specified action. Looking at
the SI stack above, we can see that APL must jump out of DIV, then REP3, then ⍎, to
return to line 7 of REPORT; THEN it takes the specified action.
Consider our system; ideally, when an unexpected error occurs, we want to save a
snapshot copy of our workspace (execute BUG in place), then immediately jump back to
REPORT and reduce our options. We can achieve this by changing our functions a little,
and using ⎕SIGNAL:
∇ REPORT;OPTIONS;OPTION;⎕TRAP
[1] ⍝ Driver functions for report sub-system. If an
[2] ⍝ unexpected error occurs, make a snapshot copy
[3] ⍝ of the workspace, then cutback the stack to
[4] ⍝ this function, reduce the option list & resume
[5] ⍝ Set global trap
[6] ⎕TRAP←(500 'C' '→ERR')(0 'E' 'BUG')
[7] ⍝ Available options
[8] OPTIONS←'REP1' 'REP2' 'REP3' 'REP4'
[9] ⍝ Ask user to choose
[10] LOOP:→END×⍳0=⍴OPTION←MENU OPTIONS
[11] ⍝ Execute relevant function
[12] ⍎OPTION
[13] ⍝ Repeat until EXIT
[14] →LOOP
[15] ⍝ Tell user ...
[16] ERR:MESSAGE'Unexpected error in',OPTION
[17] ⍝ ... what's happening
[18] MESSAGE'Removing from list'
[19] ⍝ Remove option from list
[20] OPTIONS←OPTIONS~⊂OPTION
[21] ⍝ And repeat
[22] →LOOP
[23] ⍝ End
[24] END:
∇ BUG
[1] ⍝ Called whenever there is an unexpected error
[2] '*** UNEXPECTED ERROR OCCURRED IN: ',⊃1↓⎕SI
[3] '*** PLEASE CALL YOUR SYSTEM ADMINISTRATOR'
[4] '*** WORKSPACE SAVED AS BUG.',⊃1↓⎕SI
[5] ⍝ Tidy up ... reset ⎕LX, untie files ... etc
[6] ⎕SAVE 'BUG.',⊃1↓⎕SI
[7] '*** RETURNING TO DRIVER FOR RESELECTION'
[8] ⎕SIGNAL 500
∇
Now when the unexpected error occurs, the first trap specification catches it, and the
BUG function is executed in place. Instead of logging the user off as before, an error
500 is signalled to APL. APL checks its trap specifications, sees that 500 has been set in
REPORT as a cut-back, so cuts back to REPORT before branching to ERR.
Flow Control
Error handling, which employs a combination of all the system functions and variables
described, allows us to dynamically alter the flow of control through our system, as well
as allow us to handle errors gracefully. It is a very powerful facility, which is simple to
use, but is often neglected.
It is advisable to set a trap at the top level of the application which traps all possible
errors; in this way the programmer can cater for any errors that are not already
explicitly trapped by, for example, writing information to a file, or saving the
workspace. On UNIX in particular it may also be useful to call ⎕OFF with a positive
integer to the right of the ⎕OFF - this is used as the exit code to APL.
It is also possible to generate an error which it is not possible to trap in APL code;
examples include attempting to access the session in a runtime APL, or generating an
error which causes APL to crash (for example, by the incorrect use of a shared library
function).
By default in such cases, APL will pop up a message box, and cannot continue until the
user selects the OK button.
With DYALOG_NOPOPUPS=1 APL will terminate silently, except that an aplcore file will be
generated. The location of the aplcore file can be controlled by the configuration
parameter APLCoreName. It may be more useful to ask the operating system to handle
the unexpected termination of the APL process, for example, by bringing up a
debugger, or Dr Watson. This can be achieved by setting the configuration parameter
PassExceptionsToOpSys to 1. In most cases it is useful to set DYALOG_NOPOPUPS=1 too.
It is also possible to log such events to the Windows Event Log. Setting the
configuration parameter DYALOG_EVENTLOGGINGLEVEL to a value greater than 0 will
cause this to happen. If the configuration parameter DYALOG_EVENTLOGNAME is not set,
then an event log called Dyalog will be created which can be viewed from the Windows
Event Viewer. The first time that such an event occurs the following entries will be
added to the Windows registry:
If DYALOG_EVENTLOGNAME is set, it should contain the name of the log to which events
will be logged. For example
When set, no registry entries are added by Dyalog, but if the above registry entries
have been manually created, the events will be logged to an event log which has the
name "MyApp Event Log". If the registry entries described above have not been
created, the events will instead be logged into the Application Log, and the Event
Viewer will display text similar to the following when events are viewed:
The description for Event ID ( 1 ) in Source ( MyApp Event Log ) cannot be found. The
local computer may not have the necessary registry information or message DLL files to
display messages from a remote computer. You may be able to use the /AUXSOURCE=
flag to retrieve this description; see Help and Support for details. The following
information is part of the event: Syserror: 995 code: 2 Aplcore "aplcore1" has been
created.
8 Error Messages
8.1 Error Messages
8.1.1 Introduction
The error messages reported by APL are described in this section. Standard APL
messages that provide information or report error conditions are summarised in
Section 8.1.2 and described later in alphabetical order.
APL also reports messages originating from the Operating System (WINDOWS or UNIX)
which are summarised in Section 8.1.3 and Section 8.1.4. Only those Operating System
error messages that might occur through normal usage of APL operations are described
here. Other messages could occur as a direct or indirect consequence of using the
Operating System interface functions ⎕CMD and ⎕SH or system commands )CMD and )SH,
or when a non-standard device is specified for the system functions ⎕ARBIN or
⎕ARBOUT. Refer to the WINDOWS or UNIX reference manual for further information
about these messages.
Most errors may be trapped using the system variable ⎕TRAP, thereby retaining control
and inhibiting the standard system action and error report. The table, Dyalog APL
Language: Trap identifies the error code for trappable errors. The error code is also
identified in the heading block for each error message when applicable.
See Dyalog Programming Reference Guide for a full description of the Error Handling
facilities in Dyalog APL.
clear ws
copy incomplete
defn error
incorrect command
insufficient resources
is name
name is not a ws
was name
ws not found
ws too large
1 WS FULL
3 INDEX ERROR
4 RANK ERROR
5 LENGTH ERROR
6 VALUE ERROR
7 FORMAT ERROR
10 LIMIT ERROR
11 DOMAIN ERROR
12 HOLD ERROR
16 NONCE ERROR
21 FILE FULL
23 FILE DAMAGED
24 FILE TIED
60 FULL-SCREEN ERROR
72 NO PIPES
84 TRAP ERROR
90 EXCEPTION
92 TRANSLATION ERROR
99 INTERNAL ERROR
1003 INTERRUPT
1006 TIMEOUT
1007 RESIZE
1008 DEADLOCK
See also Section 8.1.4 for the equivalent error messages under Windows.
bad ws
This report is given when an attempt is made to )COPY or )PCOPY from a file that is not
a valid workspace file. Invalid files include workspaces that were created by a version of
Dyalog APL later than the version currently being used.
This report is given when an attempt is made to )SAVE a workspace with a name that is
either the name of an existing, non-workspace file, or the name of a workspace that
the user does not have permission to overwrite or create.
clear ws
Example
)CLEAR
clear ws
copy incomplete
This report is given when an attempted )COPY or )PCOPY fails to complete. Reasons
include:
DEADLOCK 1008
If two threads succeed in acquiring a hold of two different tokens, and then each asks
to hold the other token, they will both stop and wait for the other to release its token.
The interpreter detects such cases and issues an error (1008) DEADLOCK.
defn error
• The system editor is invoked in order to edit a function that does not exist, or
the named function is pendent or locked, or the given name is an object other
than a function.
• The system editor is invoked to define a new function whose name is already
active.
• The header line of a function is replaced or edited in definition mode with a line
whose syntax is incompatible with that of a header line. The original header line
is re-displayed by the system editor with the cursor placed at the end of the line.
Back-spacing to the beginning of the line followed by line-feed restores the
original header line.
Examples
X←1
∇X
defn error
∇FOO[0⎕]
[0] R←FOO
[0] R←FOO:X
defn error
[0] R←FOO:X
⎕LOCK'FOO'
∇FOO[⎕]
defn error
DOMAIN ERROR 11
This report is given when either:
Examples
1÷0
DOMAIN ERROR
1÷0
^
(×∘'CAT')2 4 6
DOMAIN ERROR
(×∘'CAT')2 4 6
^
⎕IO←5
DOMAIN ERROR
⎕IO←5
^
EXCEPTION 90
This report is given when a Microsoft .NET object throws an exception. For details see
Dyalog APL Language: Exception.
Examples
'SALES' ⎕FSTIE 1
⎕FRDAC 1
0 4121 0
0 4137 99
X ⎕FREPLACE 1
FILE ACCESS ERROR
X ⎕FREPLACE 1
^
'SALES' ⎕FERASE 1
FILE ACCESS ERROR
'SALES' ⎕FERASE 1
^
When a new version of Dyalog APL is used, it may be that improvements to the
component file system demand that the internal structure of component files must
alter. This alteration is performed by the interpreter on the first occasion that the file is
accessed. If the operating system file permissions deny the ability to perform such a
restructure, this report is given.
FILE DAMAGED 23
This report is given if a component file becomes damaged. This rarely occurs but may
result from a computer system failure. Components files may be checked using ⎕FCHK.
See Dyalog APL Language: Fchk.
FILE FULL 21
This report is given if the file operation would cause the file to exceed its file size limit
or would cause the component number to exceed the maximum permitted which is
just below 2*32.
Example
⎕FSIZE 1
1 21 16578 4294967295
⎕FREAD 1 34
FILE INDEX ERROR
⎕FREAD 1 34
^
⎕FDROP 1 50
FILE INDEX ERROR
⎕FDROP 1 50
^
If this occurs when the file is being written it may become damaged; it is therefore
advisable to check the integrity of the file using ⎕FCHK once the source of the I/O errors
has been corrected. See Dyalog APL Language: Fchk.
This error has been seen in Windows environments which have opportunistic locks (aka
oplocks) enabled, either on the server that is running Dyalog APL, or on a server which
has access to the same shared drives, or the disk array which contains the shared
drives. In this scenario this error is not seen consistently, but rather is interspersed with
other file-related errors. Oplocks should be disabled in environments where shared
component files are used.
This error has also been seen when attempting to use component files on NFS mounted
filesystems when the NFS lock daemon is not working properly: in this state component
file operations may just hang (but can be interrupted) rather than this error being
generated.
Examples
⎕FNAMES,⎕FNUMS
SALES 1
COSTS 2
PROFIT 3
X ⎕FAPPEND 4
FILE TIE ERROR
X ⎕FAPPEND 4
^
'NEWSALES' ⎕FCREATE 2
FILE TIE ERROR
'NEWSALES' ⎕FCREATE 2
^
'EXTVFILE' ⎕XT'BIGMAT'
⎕FHOLD 'BIGMAT'
FILE TIE ERROR
⎕FHOLD 'BIGMAT'
^
⎕FHOLD⊂'BIGMAT'
FILE TIED 24
This report is given if the user attempts to tie a file that is exclusively tied by another
task, or attempts to exclusively tie a file that is already share-tied by another task.
FORMAT ERROR 7
This report is given when the format specification in the left argument of system
function ⎕FMT is ill-formed.
Example
HOLD ERROR 12
This report is given when an attempt is made to save a workspace using the system
function ⎕SAVE if any external arrays or component files are currently held (as a result
of a prior use of the system function ⎕FHOLD).
Example
∇HOLD∆SAVE
[1] ⎕FHOLD 1
[2] ⎕SAVE 'TEST'
∇
'FILE' ⎕FSTIE 1
HOLD∆SAVE
HOLD ERROR
HOLD∆SAVE[2] ⎕SAVE'TEST'
^
incorrect command
Example
)CLERA
incorrect command
INDEX ERROR 3
This report is given when either:
Examples
A
1 2 3
4 5 6
A[1;4]
INDEX ERROR
A[1;4]
^
↑ [2]'ABC' 'DEF'
INDEX ERROR
↑ [2]'ABC' 'DEF'
^
INTERNAL ERROR 99
INTERNAL ERROR indicates that the system has reached an unexpected state from
which Dyalog APL has recovered.
Should you encounter an INTERNAL ERROR, Dyalog strongly recommends that you save
your work(space), and asks that you report the issue to Dyalog.
INTERRUPT 1003
This report is given when execution is suspended by entering a hard interrupt. A hard
interrupt causes execution to suspend as soon as possible without leaving the
environment in a damaged state.
Example
1 1 2 ⍉(2 100⍴⍳200)∘.|?1000⍴200
(Hard interrupt)
INTERRUPT
1 1 2 ⍉(2 100⍴⍳200)∘.|?1000⍴200
^
is name
This report is given in response to the system command )WSID when used without a
parameter. name is the name of the active workspace including directory references
given when loaded or named. If the workspace has not been named, the system
reports is CLEAR WS.
Example
)WSID
is WS/UTILITY
LENGTH ERROR 5
This report is given when the shape of the arguments of a function do not conform, but
the ranks do conform.
Example
2 3+4 5 6
LENGTH ERROR
2 3+4 5 6
^
LIMIT ERROR 10
This report is given when a system limit is exceeded. System limits are installation
dependent.
Example
(16⍴1)⍴1
LIMIT ERROR
(16⍴1)⍴1
^
NONCE ERROR 16
This report is given when a system function or piece of syntax is not currently
implemented but is reserved for future use.
NO PIPES 72
This message applies to the UNIX environment ONLY.
This message is given when the limit on the number of pipes communicating between
tasks is exceeded. An installation-set quota is assigned for each task. An associated task
may require more than one pipe. The message occurs on attempting to exceed the
account's quota when either:
Examples
name is not a ws
This report is given when the name specified as the parameter of the system
commands )LOAD, )COPY or )PCOPY is a reference to an existing file or directory that is
not identified as a workspace.
This will also occur if an attempt is made to )LOAD a workspace that was )SAVE’d using
a later version of Dyalog APL.
Example
)LOAD EXT\ARRAY
EXT\ARRAY is not a ws
This report is given when an )NS command is issued with a name which is already in
use for a workspace object other than a namespace.
This report is given when a )CS command is issued with a name which is not the name
of a global namespace.
This report is given for each object named or implied in the parameter list of the
system command )PCOPY which was not copied because of an existing global referent
to that name in the active workspace.
Example
• An object named in the parameter list of the system command )ERASE is not
erased because it was not found or it is not eligible to be erased.
• An object named in the parameter list (or implied list) of names to be copied
from a saved workspace for the system commands )COPY or )PCOPY is not
copied because it was not found in the saved workspace.
Examples
)ERASE ⎕IO
not found ⎕IO
• When the system command )SAVE is used without a name, and the workspace is
not named. In this case the system reports not saved this ws is CLEAR WS.
• When the system command )SAVE is used with a name, and that name is not
the current name of the workspace, but is the name of an existing file.
Examples
)CLEAR
)SAVE
not saved this ws is CLEAR WS
)WSID JOHND
)SAVE
)WSID ANDYS
)SAVE JOHND
not saved this ws is ANDYS
This report is given when the limit on the number of processes (tasks) that the
computer system can support would be exceeded. The limit is installation dependent.
The report is given when an attempt is made to initiate a further process, occurring
when an APL session is started.
It is necessary to wait until active processes are completed before the required task
may proceed. If the condition should occur frequently, the solution is to increase the
limit on the number of processes for the computer system.
Example
RANK ERROR 4
This report is given when the rank of an argument or operand does not conform to the
requirements of the function or operator, or the ranks of the arguments of a function
do not conform.
Example
2 3 + 2 2⍴10 11 12 13
RANK ERROR
2 3 + 2 2⍴10 11 12 13
^
RESIZE 1007
This report is given when the user resizes the ⎕SM window. It is only applicable to
Dyalog APL/X and Dyalog APL/W.
date/time is the date and time at which the workspace was most recently saved.
Examples
)LOAD WS/UTILITY
WS/UTILITY saved Fri Sep 11 [Link] 1998
SYNTAX ERROR 2
This report is given when a line of characters does not constitute a meaningful
statement. This condition occurs when either:
Examples
A>10)/A
SYNTAX ERROR
A>10)/A
^
⊤2 4 8
SYNTAX ERROR
⊤2 4 8
^
A.+1 2 3
SYNTAX ERROR
A.+1 2 3
^
Under UNIX it may be necessary to enter a hard interrupt to obtain the UNIX command
prompt, or even to kill your processes from another screen. Under WINDOWS it may
be necessary to reboot your PC.
If this error occurs, please submit a fault report to your Dyalog APL distributor.
TIMEOUT 1006
This report is given when the time limit specified by the system variable ⎕RTL is
exceeded while awaiting input through character input (⍞) or ⎕SR.
Example
TRANSLATION ERROR 92
This report is given when the system cannot convert a character from Unicode to an
Atomic Vector index or vice versa. Conversion is controlled by the value of ⎕AVU. Note
that this error can occur when you reference a variable whose value has been obtained
by reading data from a TCPSocket or by calling an external function. This is because in
these cases the conversion to/from ⎕AV is deferred until the value is used.
TRAP ERROR 84
This report is given when a workspace full condition occurs whilst searching for a
definition set for the system variable ⎕TRAP after a trappable error has occurred. It does
not occur when an expression in a ⎕TRAP definition is being executed.
This report is given by the function editor when the number of distinct names (other
than distinguished names beginning with the symbol ⎕) referenced in a defined
function exceeds the system limit of 4096.
VALUE ERROR 6
This report is given when either:
Examples
X
VALUE ERROR
X
^
∇ HELLO
[1] 'HI THERE'
[2] ∇
2+HELLO
HI THERE
VALUE ERROR
2+HELLO
^
This warning message is reported on closing definition mode when one or more labels
are duplicated in the body of the defined function. This does not prevent the definition
of the function in the active workspace. The value of a duplicated label is the lowest of
the line-numbers in which the labels occur.
This warning message is reported on closing definition mode when one or more names
are duplicated in the header line of the function. This may be perfectly valid.
Definition of the function in the active workspace is not prevented. The order in which
values are associated with names in the header line is described in "Defined Functions
& Operators".
This report is given on opening and closing definition mode when attempting to edit a
pendant function or operator.
Example
[0] ∇FOO
[1] GOO
[2] ∇
[0] ∇GOO
[1] ∘
[2] ∇
FOO
SYNTAX ERROR
GOO[1] ∘
^
∇FOO
warning pendent operation
[0] ∇FOO
[1] GOO
[2] ∇
warning pendent operation
This warning message is reported on closing definition mode when one or more label
names also occur in the header line of the function. This does not prevent definition of
the function in the active workspace. The order in which values are associated with
names is described in "Defined Functions & Operators".
This report is given after adding or editing a function line in definition mode when it is
found that there is not an opening bracket to match a closing bracket, or vice versa, in
an expression. This is a warning message only. The function line will be accepted even
though syntactically incorrect.
Example
[3] A[;B[;2]←0
warning unmatched brackets
[4]
This report is given after adding or editing a function line in definition mode when it is
found that there is not an opening parenthesis to match a closing parenthesis, or vice
versa, in an expression. This is a warning message only. The function line will be
accepted even though syntactically incorrect.
Example
[4] X←(E>2)^E<10)⌿A
warning unmatched parentheses
[5]
was name
This report is given when the system command )WSID is used with a parameter
specifying the name of a workspace. The message identifies the former name of the
workspace. If the workspace was not named, the given report is was CLEAR WS.
Example
)WSID TEMP
was UTILITY
WS FULL 1
This report is given when there is insufficient workspace in which to perform an
operation. Workspace available is identified by the system constant ⎕WA.
Example
⎕WA⍴1.2
WS FULL
⎕WA⍴1.2
^
ws not found
This report is given when a workspace named by the system commands )LOAD, )COPY
or )PCOPY does not exist as a file, or when the user does not have read access
authorisation for the file.
Examples
)LOAD NOWS
ws not found
ws too large
• the user attempts to )LOAD a workspace that needs a greater work area than the
maximum that the user is currently permitted.
• the user attempts to )COPY or )PCOPY from a workspace that would require a
greater work area than the user is currently permitted if the workspace were to
be loaded.
The maximum work area permitted is set using the environment variable MAXWS.
This report is given when a file (which should exist) does not exist, or when a directory
in a path name does not exist.
This report is given when a device does not exist or the device is addressed beyond its
limits. Examples are a tape which has not been mounted or a tape which is being
accessed beyond the end of the tape.
This report is given when the task limit on the number of open files is exceeded. It may
occur when an APL session is started or when a shell command is issued to start an
external process through the system command ⎕SH. It is necessary to reduce the
number of open files. It may be necessary to increase the limit on the number of open
files to overcome the problem.
This report is given when a write to a file would exceed the capacity of the device
containing the file.
This report is given when an attempt is made to write to a device which can only be
read from. This would occur with a write-protected tape.
Introduction
Dyalog APL will generate a system error and (normally) terminate in one of two
circumstances:
aplcore file
When a system error occurs, APL normally saves an aplcore file which may be sent to
Dyalog for diagnosis. The name and location of the aplcore file may be specified by the
Installation/Configuration: Configuration Parameters parameter. If this parameter is
not specified, the aplcore file is named aplcore and is saved in the current working
directory.
Normally a new aplcore will replace a file of the same name. However, if Installation/
Configuration: Configuration Parameters contains an asterisk (*), the system will create
a new file, replacing the asterisk with a number incremented from the largest
numbered file present.
The number of aplcore files retained by the system is specified by the Installation/
Configuration: Configuration Parameters parameter. If Installation/Configuration:
Configuration Parameters is 0, the system will not save an aplcore. However, under
Windows, if DYALOG_NOPOPUPS is 0, and the user checks the Create an aplcore file
checkbox when the System Error dialog box is displayed, an aplcore will be saved
regardless of the value of Installation/Configuration: Configuration Parameters. See
System Error Dialog Box.
Be aware that if your application contains any secure data, this data may be present in
an aplcore file, and it may be appropriate to set both Installation/Configuration:
Configuration Parameters and DYALOG_NOPOPUPS to 0 to prevent such data being
saved on disk.
Information that may prove useful in debugging the problem, including (where
possible) the SI stack at the point where the aplcore was generated, is by default
written to the end of aplcore files; the section begins with the string
Under UNIX, this interesting information section can be extracted from the aplcore as
follows:
To prevent this information from being written to the aplcore file, the Installation/
Configuration: Apl Textinaplcore parameter should be set to 0.
Workspace Integrity
When you )SAVE your workspace, Dyalog APL first performs a workspace integrity
check. If it detects any discrepancy or violation in the internal structure of your
workspace, APL does not overwrite your existing workspace on disk. Instead, it displays
the System Error dialog box and saves the workspace, together with diagnostic
information, in an aplcore file before terminating.
A System Error code is displayed in the dialog box and should be reported to Dyalog for
diagnosis. This information also appears in the Interesting Information section of the
aplcore file.
Note that the internal error that caused the discrepancy could have occurred at any
time prior to the execution of )SAVE and it may not be possible for Dyalog to identify
the cause from this aplcore file.
If APL is started in debug mode with the -Dc, -Dw or -DW flags, the Workspace Integrity
check is performed more frequently, and it is more likely that the resulting aplcore file
will contain information that will allow the problem to be identified and corrected. It is
also possible to enable or alter the debugging level from within APL using the SetDFlags
method; Dyalog support will direct the use of this feature when necessary.
System Exceptions
Non-specific System Errors are the result of Operating System exceptions that can occur
due to a fault in Dyalog APL itself, an error in a Windows or other DLL, or even as a
result of a hardware fault. The following system exceptions are separately identified.
Objects may often (but not always) be recovered from aplcore using )COPY or ⎕CY. Note
that if the aplcore contains a workspace with more than one instance of the same
name on the stack, ⎕CY copies the most local object whereas )COPY copies the global
one.
Be aware that in many cases an attempt to )COPY from or )LOAD an aplcore is likely to
result in a further syserror; this may result in the original aplcore being overwritten,
thus losing the contents of that file. It is therefore worth while taking a copy of the
aplcore before attempting to )COPY from it. Attempting to copy specific items is more
likely to be successful than copying the entire workspace from the aplcore.
Note that in previous versions under Windows because (by default) the aplcore file has
no extension, it was necessary to explicitly add a dot, or APL would attempt to find the
non-existent file [Link]. This is no longer true in version 14.1 onwards.
If APL crashes and saves an aplcore file, please email the following information to
support@[Link]:
If the problem is reproducible, that is, can be easily repeated, please also send the
appropriate description, workspace, and other files required to do so.
The System Error Dialog illustrated below was produced by deliberately inducing a
system exception in the DLL function memcpy(). The functions used were:
∇ foo
[1] goo
∇
∇ goo
[1] hoo
∇
∇ hoo
[1] crash
∇
∇ crash
[1] ⎕NA'dyalog64|MEMCPY u u u'
[2] MEMCPY 255 255 255
∇
Note: Under a 32-bit interpreter the ⎕NA call should refer to dyalog32.
Options
Item Description
Create a process
Dumps a complete core image, see below.
dump file
If you check this box (only enabled on System Error codes 995
Create
and 996), APL will not terminate but will instead generate an
Trappable Error
error 91 ( EXTERNAL DLL EXCEPTION ) when you press Dismiss .
Create an
If this box is checked, an aplcore file will be created.
aplcore file
Pass exception If this box is checked, the exception will be passed on to your
on to operating current debugging tool (for example, Visual Studio ). See
system PassExceptionsToOpSys .
Copy to Copies the contents of the APL stack trace window to the
clipboard Clipboard.
Under Windows the Create a process dump file option creates a user-mode process
dump file , also known as a minidump file, called [Link] in the current directory.
This file allows post-mortem debugging of a crash in the interpreter or a loaded DLL. It
contains much more debug information than a normal aplcore (and is much larger than
an aplcore) and can be sent to Dyalog Limited (zip it first please). Alternatively the file
can be loaded into Visual Studio .NET to do your own debugging.
If you are using Visual Studio, the following procedure should be used to debug your
own DLLs when an appropriate Dyalog APL System Error occurs.
Ensure that the Pass Exception box is checked, then click on Dismiss to close the System
Error dialog box.
The system exception dialog box appears. Click on Debug to start the process in the
Visual Studio debugger.
After debugging, the System Exception dialog box appears again. Click on Don't send to
terminate Windows' exception handling.
ErrorOnExternalException Parameter
This parameter allows you to prevent APL from taking the actions described above
when an exception caused by an external DLL occurs. The following example illustrates
what happens when the functions above are run, but with the Installation/
Configuration: Configuration Parameters parameter set to 1.
If Dyalog for Windows hangs, you should generate a process dump file and send it to
Dyalog Support, along with your Build ID.
To do this:
9 APLMon
9.1 Introduction
APLMON is a built-in Dyalog feature that, when enabled, monitors the execution of APL
primitives and functions and writes the results to a CSV file. When not enabled (the
default), it has no impact on execution.
When running an APL application with APLMON enabled, Dyalog logs the use of every
primitive operation; how many times it was called, how much time was spent running it
and details about the arguments it was processing. This makes it possible to identify
the primitive operations on which the application spends the most time. Dyalog Ltd can
use this data to help determine which optimisations to prioritise and where to
concentrate performance work. For additional information on this topic, please contact
support@[Link].
APLMON does not return any application-side information. APL programmers should
use ⎕PROFILE or the cmpx function from the dfns workspace to determine which parts
of the application can be optimized programmatically. However, APLMON can be useful
for APL programmers who want to verify that their application uses primitives as
expected or that it does not perform unnecessary computations such as disclosing
simple scalars.
Accumulated data is only actually written to the file when the method is called or when
Dyalog terminates. The name of the file to which the results are written is returned as a
shy result. This mechanism allows you to log application results in separate files.
Example
⍴a
0
The empty result indicates that APLMON was disabled prior to this call.
Example
The file name in the result indicates that data has been output to that file and APLMON
has been disabled.
The overhead of using APLMON reduces the overall speed of execution significantly.
However, this does not affect the relative accuracy of the measurements.
For optimal accuracy, time measurement should be done with as few other processes
running as possible, and in a process that has a higher priority than others.
To change process priority on Microsoft Windows: Start Dyalog, then launch the Task
Manager. In the Processes tab, right-click on Dyalog and select Go to details from the
pop-up menu that appears. Right-click on [Link] in the Details tab; move the
cursor to Set priority in the pop-up menu that appears, then select Above normal from
the additional pop-up menu. A higher priority is not needed (everything else is Normal
by default) and if set higher, the Dyalog process could cause the operating system to
hang and make user control difficult.
These fields are empty if not applicable (which will appear in the CSV file as two
consecutive commas).
Example
In the following example, APLMON is used to analyse the execution of the expression
10 foo 20 30. Note that expressions executed in the Session when APLMON is
enabled also contribute to the analysis.
∇foo∇
∇ r←a foo b
[1] r←(a+b)(a-b)(a×b)(a÷b)
∇
⊢2 ⎕NQ '.' 'APLMON' 'c:\dyalog17.0\aplmon'
10 foo 20 30
30 40 ¯10 ¯20 200 300 0.5 0.3333333333
⊢ 2 ⎕NQ '.' 'APLMON' ''
rslt←⎕CSV 'c:\dyalog17.0\aplmon'
⍴rslt
15 11
rslt
token lfn rfn ltype rtype lrank rrank lboun
d rbound hitcount time
plus int8 int8 0 1
1<1 2<4 1 0.000001
minus int8 int8 0 1
1<1 2<4 1 0.000001
times int8 int8 0 1
1<1 2<4 1 0.000002
divide int8 int8 0 1
1<1 2<4 1 0.000003
left arrow nested
1 2<4 1 0.000001
right tack wchar8
1 0<0 1 0.000000
stranding int16 float64 1 1
2<4 2<4 1 0.000001
stranding int8 nested 1 1
2<4 2<4 2 0.000001
stranding wchar8 wchar8 1 1
5<9 0<0 1 0.000001
name
lookup
10 0.000002
tradfn
init
1 0.000002
aplmon measurement
time
0.000013
aplmon unmeasured
time
19.772147
aplmon
total
19.772174
APLMON measures time only for the atomic calls (that is, not complex expressions or
user-defined functions). Primitives that evaluate APL expressions (such as ⍎, ⎕FIX and
⎕NEW) are not included. For example :
foo←{1+⍵} ⋄ foo¨(N⍴1)
The each will not appear in the log, but only the primitives called by foo, meaning that
+ will appear as being called N times
+.×/(N⍴1
The / will not be measured, but +.× will appeared as called (N-1) times.
This ensures that time measurements do not overlap, and can therefore be compared.
However, some non-atomic expressions are logged, and the non-primitive parts are
logged as non primitive.
For example, cases of Simple Indexing (where the indexer is a set of semicolon-
separated simple arrays) are reported as follows.
The token is simple indexing, there are no lfn or rfn, the left argument is the indexed
array, and the right argument is as representative of the indices as possible. Dyalog has
chosen to make it appear as an array, the rank of which is the number of specified
subscripts, and the shape of which is the number of items in these subscripts.
APL
token lfn rfn ltype rtype lrank rrank lbound rbound
expression
'abcd'[3 simple
unicode8 int8 1 1 2<4 5<9
3⍴3] indexing
(2 2⍴4) simple
int8 int8 2 1 2<4 1<1
[1;] indexing
The token is reach indexing, the left argument is the indexed array, and the right
argument is the indexer array.
Axis specification
The token is axis, lfn is the function whose axis is specified, rfn is non-primitive, as it
is the axis specification constant.
Assignment
Modified Assignment
The token is left arrow, lfn is the modifier function, rfn is empty, larg is the modified
value and rarg the modifier value.
Indexed Assignment
The token is indexed assignment, lfn and rfn are empty, larg is the indexed array, rarg
is the new value.
The token is indexed assignment, lfn is the modifier function, rfn is empty, larg is the
indexed array, rarg is the new value.
Selective Assignment
The token is selective assignment,lfn and rfn are empty,larg is the indexed array,
rarg is the new value.
The token is selective assignment, lfn is the modifier function, rfn is empty, larg is
the indexed array, rarg is the new value.
Stranding
The token is stranding, larg and rarg are the arrays being stranded together.
Name Lookup
The token is name lookup. This represents time spent in the parser looking up names in
namespaces.
The token is tradfn init. This represents time spent on entry to a traditional defined
function.
The token is selassi pre-scan. This represents extra time spent in the parser to
analyse the syntax of a selective assignment expression.
Control Structures
The token is control structures. This represents time spent processing control
structures in a traditional defined function.
The token is nsref, lfn and rfn are right and left operands respectively, larg and rarg
are as expected.
token is nsref, lfn is ⎕EX, rfn is non-primitive, larg is empty, rarg is the character
vector.
9.5 Notes
Indexing
Simple indexing is the case when the indexer is a list of semicolon-separated simple
arrays, as opposed to choose and reach indexing, where indexer is a nested array:
A[;;2;(2 3⍴1)]
A[⊂((2 1) 1 5)]
For choose and reach indexing, the indexer will be represented by itself, in the monitor
log, because it is a true array. A simple indexer, though, is not a true array but a
collection of arrays, so it is treated specially : it is represented in the monitor log by a
character array, the rank of which is the number of subscript, and the shape of which is
the number of items in each subscript.
Indexer Examples
In the monitor, for indexing, A[I], the left argument will be A and the right argument
will be the representation of I as described above.
For indexed assignment, A[I]←B, the right argument will be B, and the left argument
will be the representation of I as described above.
Operators
Examples
∇ APLMON expr
[1] 2 ⎕NQ'.' 'APLMON' 'c:\dyalog17.0\aplmon'
[2] ⍎expr
[3] ⎕CSV 2 ⎕NQ'.' 'APLMON' ''
∇
total
0.003500
APLMON '+.×/1 2 3'
6
token lfn rfn ltype rtype lrank rrank lbo
und rbound hitcount time
dot plus times int8 int8 0 0
1<1 1<1 1 0.000007
dot plus times int8 int32 0 0
1<1 1<1 1 0.000001
stranding wchar8 wchar8 1 1
5<9 0<0 1 0.000001
name
lookup
1 0.000001
aplmon measurement
time
0.000005
aplmon unmeasured
time
0.003058
aplmon
total
0.003073