APL2 Programming - GUIDE
APL2 Programming - GUIDE
Guide
Version 2 Release 1
SH21-1072-00
APL2 Programming: IBM
Guide
Version 2 Release 1
SH21-1072-00
Note!
Before using this information and the product it supports, be sure to read the general information under “Notices”
on page v.
This edition applies to Release 1 of APL2 Version 2, Program Number 5688-228, and to any subsequent releases until otherwise
indicated in new editions or technical newsletters.
Changes are made periodically to this publication; before using this publication in connection with the operation of IBM systems,
consult the latest edition of the applicable IBM system bibliography for current information on this product.
Requests for IBM publications should be made to your IBM representative or to the IBM branch office serving your locality. If you
request publications from the address given below, your order will be delayed because publications are not stocked there.
A Reader's Comment Form is provided at the back of this publication. If the form has been removed, comments may be addressed
to IBM Corporation, Department J58, P. O. Box 49023, San Jose, California, U.S.A. 95161-9023. IBM may use or distribute what-
ever information you supply in any way it believes appropriate without incurring any obligation to you.
Copyright International Business Machines Corporation 1984, 1992. All rights reserved.
US Government Users Restricted Rights – Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
Contents
Notices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v
Programming Interface Information . . . . . . . . . . . . . . . . . . . . . . . . . . . v
Trademarks and Service Marks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v
Part 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Chapter 2. Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
APL2 Is Interactive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
APL2 and You Take Turns at the Terminal . . . . . . . . . . . . . . . . . . . . . 3
Characteristics of Terminals Used with APL2 . . . . . . . . . . . . . . . . . . . 3
What Can be Entered and What Gets Displayed . . . . . . . . . . . . . . . . . 4
The APL2 Character Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Review of Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Beyond Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Chapter 10. Formatting the Invoice: The INVOICE and FORMAT Routines . 72
Getting the Invoice Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Formatting the Report . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
History Sheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
IBM may have patents or pending patent applications covering subject matter in
this document. The furnishing of this document does not give you any license to
these patents. You can send license inquiries, in writing, to the IBM Director of
Commercial Relations, IBM Corporation, Purchase, NY 10577.
If you're an experienced APL user, you might skip Part 1 entirely, or focus only on
those areas in the “Fundamentals” chapter that cover features that are new or dif-
ferent with APL2. Two new features are defined operators, introduced in the
“Operators” section, and nested arrays, introduced in the “Arrays” section.
In Part 2, this book shows you how you can use some of APL2's features to
program an inventory control application. It describes the nature of the application,
discusses how you might structure the data, and then illustrates the coding details.
The foldout section of Part 2 (inside the back cover of the book) shows all of the
functions, variables, and operators used in the sample implementation.
As you become proficient in writing APL2 applications, you'll probably need APL2
Programming: Language Reference. That book describes the APL2 language in
depth and gives a number of examples.
Figure 1 on page vii shows which APL2 publications should be consulted for help
with various tasks.
Related Publications
IBM DATABASE 2 Application Programming Guide for TSO Users, SC26-4081
IBM DATABASE 2 Data Base Planning and Administration Guide, SC26-4077
IBM DATABASE 2 Introduction to SQL, SC26-4082
SQL/Data System Application Programming, SH24-5018
SQL/Data System Planning and Administration, SH24-5043
The language that APL2 accepts is an extension of the language that previous APL
products accepted. For instance, an array (the basic unit of data in APL) can mix
numbers and characters. Previously an array could have either numbers or charac-
ters, but not both. This feature simplifies commonly performed tasks such as dis-
playing tables of data with text headings. Furthermore, APL2 allows the pieces of
data in an array, the array items, to be arrays themselves. This allows program-
mers to request operations on complicated collections of data (for instance, part
inventory records) in the same way that they request operations on more uniform
collections of data. In fact, APL2 is designed to help users concentrate on the
result they want an operation to accomplish, and to free them from concern for the
structure of the data.
These features and others make APL2 well suited not only to the professional busi-
ness programmer and the advanced scientific and technical user, but also to the
occasional user with little or no previous experience with computers.
APL2 Is Interactive
APL2 takes one APL statement at a time, executes it, and then proceeds to the
next line. Contrast this sequence with traditional program compilers that convert
complete programs to machine language before executing any statements. This
allows you a high degree of interaction with the computer. If something that you
enter is invalid, you will get quick feedback on the problem before you proceed
further.
When APL2 displays information for you, it starts each new line at the left margin.
After it finishes displaying any such output, it signals that it is ready for you to type
in another keyboard input by spacing in six spaces from the left margin and halting.
This indented position indicates that “it's your turn.” For example:
2+2 You typed this statement in
4 APL2 executed the request
─────────────────────── Now you can enter something else
All examples in this manual are presented as they would appear on an IBM*
3270-series display terminal, with one exception: Alphabetic characters are shown
in this book in italic font. This is to help you differentiate between explanatory text
and characters you might see on your terminal screen, even though your terminal
might display upright block characters instead of italics. Here's an example:
A←ONE1 TWO2 THREE3
A
ONE1 TWO2 THREE3
The effect of this entry is to assign to the name AREA the value of the expression
34.
This line is termed a statement. Because the label and comment are optional,
calling AREA←34 a statement is correct.
In any case, the statement may be read informally as “AREA gets three times
four.”
A statement may or may not display a result. For example, the following statement
displays a result:
34
12
The characters fall into four main classes: alphabetic, numeric, special, and blank.
The alphabetic characters include the Roman alphabet in uppercase italic font, the
same alphabet underscored, plus δ, and .
Chapter 2. Fundamentals 5
The numeric characters are 0 through 9.
Lowercase characters are special characters, and are thus not used in APL2
names.
Figure 3 also lists the overstrike characters. The overstrike combinations may be
entered in either order, with a backspace in between. On an IBM 3270 terminal,
you can't overstrike a character in the sense of superimposing one character above
the other. If you try, you'll merely replace one character with the other. Fortu-
nately, you can get most of the overstrike characters without actually overstriking;
for instance, and correspond to single keys on the keyboard.
On some display terminals, there may be no way to enter the overstrike characters.
Therefore, APL2 allows you to designate the underscore (]) as a backspace char-
acter in the context of these characters. For example, you can enter the equal
underbar character (¢) by pressing the = key, the ] key (for a backspace), and
the ] key again (this time, for an underbar).
When you start a session with APL2, the backspace character is on. You can
make sure of this by entering the following system command:
)PBS
IS ]
If you see the response is IS OFF, you can turn the backspace character back
on by entering:
)PBS ON
Now, if you want to check whether array A is the same as array B, you can enter:
A=]]B
1
You use )PBS to enter the 10 overstrike characters even if you are on a terminal
that uses program symbol sets. However, if you use the symbol set provided by
APL2, the session manager will display the correct character. Thus, if you enter
the expression above, it will change to the following when you press the ENTER
key:
A¢B
1
Numbers
All numbers entered or displayed are in decimal. They can be in conventional
form (including a decimal point if appropriate):
257
257
0346
0346
or in scaled form:
257E2
257
346E3
0346
In similar fashion, APL2 accepts and displays complex numbers, with a J sepa-
rating the real and imaginary parts. For example, the square root of negative one
can be entered or displayed as:
Optionally, a polar form is available for entry, with the angle expressed in either
radians or degrees. In polar form, the square root of negative one can be entered
as:
The MATHFNS workspace, distributed with APL2, contains functions that provide
displays for radians and degrees.
Furthermore, either the real or imaginary part of a complex number can be entered
in scaled form:
12E5J4E4
8E3D1E2
Notice that APL2 does not display complex numbers in polar form.
Chapter 2. Fundamentals 7
Negative numbers are represented by an overbar immediately preceding the
number:
257
257
346E3
0346
The overbar can be used as part of a numeric constant and is distinguished from
the bar that denotes negation, as in |X. The overbar may not be used to denote
negation of a value assigned to a name; that is, X is invalid.
Functions
The word “function” comes from a word that means to execute or to perform. A
function applies to some data (its arguments) and transforms it into new data (its
result).
A function that applies to one argument (one array) is called a monadic function.
For example, the function interval (represented by the iota symbol) applied to the
number 7 produces a list of the first seven integers.
ι7
1 2 3 4 5 6 7
A function that applies to two arguments (two arrays) is called a dyadic function.
For example, multiply applied to two lists produces a new list.
2 3 4 10 20 30
20 60 120
Functions that are denoted with symbols are called primitive functions. You may
also define your own functions to do computations not provided by the primitive
functions.
Operators
You can change the normal action of a function by applying an operator to it. For
example, + and are primitive functions; applying the reduce operator (/) to
produce +/ and / modifies their normal operation in a precise, defined manner,
and produces a new, derived function:
The value of an operator is that you may use it to produce a whole set of related
derived functions, each of which is applied in precisely the same way. For
instance, in the above example, +/ produced the function summation, which,
when applied to the vector 3 4 5, produced the sum 12. This was the equiv-
alent of 3+4+5.
/3 4 5 Product
60
[/3 4 5 Largest
5
+/¨ (3 4 5) (9 8 7 6)
12 30
Another useful operator is outer product. It applies a function between all combi-
nations of items, one from the left argument and one from the right argument. For
example:
(ι3) `ι4
1 2 3 4
2 4 6 8
3 6 9 12
Operators that are denoted with symbols are called primitive operators. You may
also define your own operators. This is new in APL2 and is an extremely powerful
facility. Following is a defined operator that simulates the reduce function:
[0] Z←(F RED) R
[1] Z←F/R
+ RED 2 3 4
9
Remember:
A function applies to one or more data objects and returns a data result.
An operator applies to one or more data objects or functions and returns a
function.
Arrays
An array is an ordered collection of data. Each item of data can be a number, a
character, or another array. For example, all the following are arrays:
2 3 5 7 9
ADAMS 5 8 1 3 2
Chapter 2. Fundamentals 9
The Structure of Arrays
Arrays have structure; the data in an array is ordered along zero or more directions,
called axes. The number of axes that an array has is its rank. For example, a
simple list of numbers has only one axis and therefore is of rank one:
V
2 3 5 7 11 13 17 19
Either of these examples could as easily have used character data, or a mixture of
numeric and character data. For instance:
MIX
A B C D
1 2 3 4
9 E 10 11
N←R
N
R
The shape of an array is the number of data items in each axis of the array. You
can measure an array's shape by using the shape function, denoted by the ρ
(rho) symbol:
V
2 3 5 7 11 13 17 19
ρV
8
A
ABCDEFGH
ρA
8
ρV Shape of V
8
ρρV Rank of V
1
Because a vector has one axis, its shape is one number — actually, a one-item
vector. The shape of a matrix, because it has two axes, is a two-item vector:
N
1 2 3 4
5 6 7 8
9 10 11 12
ρN
3 4
ρρN
2
Notice that the last item (that is, the rightmost item) of the shape vector is the
number of columns in the array, and the next-to-last item is the number of rows.
In all the previous examples, the data items in an array have been simple scalars,
that is, single numbers or characters, but they don't have to be:
NAMES
ADAMS 5 8 1 3 2
ρNAMES
3
ρρNAMES
1
In this example, the first item of NAMES is the character vector ADAMS, the
second item is the scalar 5, and the third item is the numeric vector 8 1 3 2.
Such an array is called a nested array. A nested array is an array in which one or
more data items is not a simple scalar, If all the items are single numbers or char-
acters, (as they are in array N above), the array is called a simple array.
In general, it is difficult to tell what the exact structure of a nested array is from its
formatted output. APL2 provides a defined function (the display function) that
gives a picture of an array. To get the display function, issue a command of the
form:
(The number n is usually 1, but your system administrator might have designated
another number.) For more information about the display function, see APL2 Pro-
gramming: Language Reference.
Chapter 2. Fundamentals 11
Let's display the structure of NAMES:
DISPLAY NAMES
→||||||||||||||||||||
→|||| →||||||
ADAMS 5 8 1 3 2
||||| *||||||
ε||||||||||||||||||||
The display shows each vector item enclosed within a box. The outer box repres-
ents the entire vector NAMES. The → in the display indicates that what is in the
box is a vector, the ε indicates that the array contains at least one item that isn't a
simple scalar, and the * indicates numeric data.
Here's another example of an array whose data items aren't simple scalars:
ATTENDANCE
NAME DAY1 DAY2 DAY3
JONES Y N N
SMITH N Y Y
TAYLOR Y Y Y
ρATTENDANCE
4 4
ρρATTENDANCE
2
DISPLAY ATTENDANCE
→||||||||||||||||||||||||||||||
↓ →||| →||| →||| →|||
NAME DAY1 DAY2 DAY3
|||| |||| |||| ||||
→||||
JONES Y N N
||||| | | |
→||||
SMITH N Y N
||||| | | |
→|||||
TAYLOR Y Y Y
|||||| | | |
ε||||||||||||||||||||||||||||||
The ↓ in the display indicates that ATTENDANCE is a matrix. Notice that each
item in the first row of ATTENDANCE is a character vector and each item in the
first column is a character vector. The ] under the Ys and Ns indicates that these
are scalar characters at the same depth as the vectors JONES, SMITH, and
TAYLOR.
¢N
1
If the deepest item in an array has a depth of 1, the array has a depth of 2:
ATTENDANCE
NAME DAY1 DAY2 DAY3
JONES N Y N
SMITH Y N Y
TAYLOR Y Y Y
¢N
2
A depth of 3 indicates that the item of greatest depth in the array has a depth of 2.
For example:
STOCK
ITEM DETAIL1 DETAIL2
BOLT 118 47 52 119 495 525
FRAME 729 228 24 730 235 24
TUBE 23 125 125 25 13 135
¢STOCK
3
Because you can't determine depth from what is printed, let's use the display func-
tion to show the structure of STOCK:
Notice that the second, third, and fourth rows of STOCK include items that are
depth-2 vectors. For instance, the second item in the BOLT row is a nested
vector. The first item in the nested item is 118, and the second item is the vector
47 52.
Note: An easy way to tell the depth of an item is to count the minimum number of
lines an arrow would need to cross to reach the item from the outside of the
display. For example, an arrow would cross two lines to reach BOLT; therefore, it
is depth 2. An arrow would cross three lines to reach 125 125; therefore, it
is depth 3.
Chapter 2. Fundamentals 13
Forming Arrays
A vector of length two or more may be formed by listing the arrays that are its
items. The items must be separated by a blank or a parenthesis.
2 3 5 7 Four numbers
2 B 5 D Numbers and letters
A B C D Four letters
If every item of a vector is a single character, the vector may be written with a
single pair of enclosing quotation marks. Thus the last example above may be
written:
ABCD
Any item of a vector may be the result of a computation as well as a constant. For
example, the following two vectors are equivalent, and one may be used wherever
the other is used:
2 3 (2+3) 7
2 3 5 7
Any item of a vector may be another vector or any other array. Here is a vector
containing vectors:
(2 3 5) (7 11 13)
2 3 5 7 11 13
To form a vector of length one or zero, or an array of rank greater than one, you
must apply some functions. The most fundamental function is reshape (ρ), which
produces an array of requested shape containing the given items. Here's a simple
matrix of numbers:
N←2 3 ρι6
N
1 2 3
4 5 6
1 2 3 3 4
4 5 6
Given a matrix you can turn it into a vector. The function ravel (,) produces a
vector having the same number of items as its argument:
,M
1 X 1 2 3 3 4
4 5 6
ρ,M
4
The function enlist (ε) produces a vector containing all the simple scalars from its
argument:
A nested vector may be turned into a matrix by using the disclose () function:
(1 2 3) (4 5 6)
1 2 3
4 5 6
ρ(1 2 3) (4 5 6)
2 3
Sometimes the display of a nested vector is too wide to fit properly on the screen.
The function ravel with axis may be used to turn the vector into a one-column
matrix. Example:
V←A VERY LONG NESTED VECTOR
V
A VERY LONG NESTED VECTOR
,[ι0]V
A
VERY
LONG
NESTED
VECTOR
In the application in Part 2, you will find several one-column nested matrices. They
are represented this way because of the way they display. In a real application,
you might leave them as vectors.
The code you'll see is designed for your personal use — because you never make
mistakes! Functions designed for people who do make mistakes would need more
error checking and trapping like those discussed in Chapter 5, “Writing Input and
Error-Checking Routines” on page 31. We'll discuss here some techniques for
building a data structure. Each begins from a structure having no data, and then
progressively adds data based on entries from the keyboard.
For example, if the table contained banking data, each row could represent one
account, and the columns could represent current balance, customer name, and so
forth. Such a table is sometimes called a relation.
Although the techniques shown below may be used for structures more general
than these, we will concentrate on relational data.
Chapter 2. Fundamentals 15
Let's suppose we want to build the STOCKS matrix used in Part 2 of this book. It
is a five-column array of numbers, except for column 2, which is made up of char-
acter vectors.
Here's an example:
STOCKS←0 5ρ0 0 0 0
STOCKS
(blank) It's empty
ρSTOCKS
0 5 Has five columns, but no rows
ρρSTOCKS
2 And it's a matrix
You might wonder how this empty matrix differs from this one:
STOCKS←0 5 ρ0
The answer is — no difference at all. The reason we recommend the first form is
that it documents the intended use of the columns. Also, you can write functions
that use the column types and they will work even when the table is empty.
Now that STOCKS exists, you can add rows to it, but because the empty
STOCKS has no rows, the first row you add will be the first row in the array:
You could enter more statements like this to add more rows to the matrix, but it
would be a time-consuming process involving a lot of typing.
Instead, you could write a small function to read input and catenate it to the matrix.
In addition to needing less typing, the function could do some error checking.
^ Z←EVALIN;X
[1] ACCEPT INPUT LINES UNTIL AN EMPTY LINE IS INPUT
[2] Z←
[3] L1:Z←Z,X← ACCEPT INPUT ; APPEND TO RESULT
[4] →(0≠ρX)/L1 REPEAT IF INPUT NOT EMPTY
[5] →(0=ρZ←1↓Z)/0 DROP EMPTY ENTRY ; EXIT IF NO INPUT
[6] Z←¨Z EVALUATE AND RETURN A MATRIX
The last line of EVALIN evaluates each character input line with and creates a
matrix with .
Now we have a matrix that has the required data. It is 5 columns wide:
ρSTOCKS
3 5
DISPLAY STOCKS
→||||||||||||||||||||||||||||||||||||||
↓ →|||||||||||||||
1135 FIRST GREAT ITEM 995 118 55
||||||||||||||||
→||||||||||||||||
9993 HIGH FLYER WIDGET 8873 240 35
|||||||||||||||||
→||||||||||||||||
3569 SECOND MONEYMAKER 2475 0 30
|||||||||||||||||
ε||||||||||||||||||||||||||||||||||||||
TAB←0 0 ρ
Notice that in this case we don't worry about the number of columns. EDITOR 2
will expand the number of columns as necessary.
Chapter 2. Fundamentals 17
You invoke the editor by entering:
^TAB
Now you can use all the power of the editor to add, delete, change, or rearrange
lines. Your screen would look like this:
[]^ TAB2 ρ: 0 0
[0] TAB
[]^ TAB2 ρ: 3 37
[0] TAB
[1] 1135 FIRST GREAT ITEM 995 118 55
[2] 9993 HIGH FLYER WIDGET 8873 240 35
[3] 3569 SECOND MONEYMAKER 2475 0 30
When you close definition, the variable TAB has a character matrix representing
what you last saw on the screen.
We can now use a function to turn the character matrix into the data we want:
^ Z←EVAL M
[1] EVALUATE EACH ROW OF A MATRIX
[2] Z←¨[1+{IO]M
STOCKS←EVAL TAB
STOCKS←¨[1+IO]TAB
V←2 3 5 7 11 13 17 19
V[3 1 5] Selecting items from a simple array
5 2 11
X←3 1 5
V[X]
5 2 11
Notice that the position indicators are simple arrays. For arrays of rank two or
higher, APL2 needs more than one position indicator to identify the position of indi-
vidual items. In these cases, the index (that is, the arrays in the brackets) is a
M←2 4ρV
M
2 3 5 7
11 13 17 19
M[2;3]
17
M[2 1;2 3 4]
13 17 19
3 5 7
ρM[2 1;2 3 4]
2 3
and:
T←2 2 3ρABCDEFGHIJKL
T
ABC
DEF
GHI
JKL
T[2;1;3]
I
T[2;1 2;1 2 3]
GHI
JKL
ρT[2;1 2;1 2 3]
2 3
Notice that a composite index selects all the items along the pertinent axes. Thus,
M[2 1;2 3 4] selects the items in row 2, columns 2, 3, and 4, and in row 1,
columns 2, 3, and 4.
Also notice that the shape of the result in these examples is the same as the shape
of the indexes. This is true of bracket indexing in general. Consequently, bracket
indexing gives you a way of making results conform to the shape you want:
Chapter 2. Fundamentals 19
N
1 2 3 4
5 6 7 8
9 10 11 12
`{[1+N>6]
````
``{{
{{{{
The previous example points out another thing about bracket indexing: The index
values can be repeated. In the previous example, APL2 evaluates the expression
for each item of N. For items less than or equal to 6, the expression becomes
`{[1], so that the ` is selected. For items greater than 6, the expression
becomes `{[2], so that { is selected.
One last word on this subject: Items can't be selected from a scalar through
bracket indexing, because a scalar has no axes from which to select its data.
Suppose you wanted to select the 7. That 7 is item 3 of a vector; that vector is at
row 2 column 3 of a matrix; and that matrix is at row 2 column 1 of P. This infor-
mation is packaged into the left argument of pick as follows:
(2 1) (2 3) (3)P
7
Notice that 7 is at depth 3, the arrow pointing at the 7 crosses 3 lines, and the left
argument of pick is a 3-item vector. In general, a length n left argument will pick an
item at depth n. The vector is sometimes called a path to the item. Also notice
that, at each level, the rank of the item determines the number of integers needed
to pick from it.
Let's look at some other examples. Suppose you wanted to select the entire vector
9 8 7.
If you wanted to select the entire matrix containing the vector, a length-1 index will
do. Because there is no notation for writing a length 1 or 0 vector, we must use a
function to get one item. Here we use enclose to produce a scalar:
( 2 1)P
1 2 3
4 5 9 8 7
In general, if you want to pick an item from a matrix, you must enclose the row and
column index.
By the way, you can write these expressions on the left of a left arrow and replace
an item instead of selecting it. The following example:
will replace the vector 9 8 7 with a character vector, called selective specifica-
tion.
As you have seen, pick can be used to select a single item at some specified
depth. Suppose you wanted to select two items from P, as created on the previous
page?
I←(2 1) (2 3) (3)
IP
7
J←(2 2) (1)
JP
3
This is an extremely common idiom (it's sometimes called the “chipmunk idiom”).
Enclose of P (P) is a scalar. Pick each is a scalar function, so a scalar argument
(the right argument of pick each in this case) extends. Thus this one expression is
equivalent to both the previous expressions.
Note: ¨ cannot be used on the left of a left arrow.
Beyond Fundamentals
In the next part of this book you'll see how you can apply APL2 to meet a data
processing need. In particular, you'll learn how you can use APL2 in a business
application.
If you want to read more about APL2 fundamentals, read “Fundamentals” in APL2
Programming: Language Reference, before you go on to Part 2 of this Program-
ming Guide. That part of the Language Reference gives a comprehensive
description of such topics as arrays and evaluating expressions. You'll find that this
in-depth coverage of APL2's fundamentals will make it easier for you to follow the
application as it develops in the next part of the Programming Guide.
Also consult the Language Reference for detailed information on the following:
Structure, definition, and execution of defined functions and operators
The APL2 editors
Complex arithmetic
Messages
Chapter 2. Fundamentals 21
System commands
System functions and variables
Use of auxiliary processors
The following chapters present the structure and content of a set of APL2 variables,
functions, and operators that you can use to implement a model inventory control
application. The introduction of techniques is the sole purpose of the application
presentation — it is not intended to present an application that will fulfill the needs
of a “real world” inventory control application. For your convenience in following the
discussion, the foldout pages inside the back cover of the book show all the func-
tions, variables, and operators used in the sample implementation. We will refer to
them as we discuss each part in detail.
You'll get the most out of this application if you try things out at your own terminal
as we go along.
As APL2 primitive functions and operators are introduced, they will be shown in
boldface type. For further information about them, refer to APL2 Programming:
Language Reference.
After getting over your initial anxiety, you begin to think, “What would an automated
order and inventory system do? How might customers and order clerks use it?”
Figure 4 shows the layout of the store: The desks that deal with placing orders,
filling them, and entering restocking information into the computer system.
A customer walks into the store, looks in the catalog, and fills out an order form.
The form includes the customer's name and address as well as information about
the items she wants to buy. Figure 5 shows a customer order.
Name: ───────────────────────────────────────────────────────────────────────────────────────────
Address: ────────────────────────────────────────────────────────────────────────────────────────
City, State, Zip: ───────────────────────────────────────────────────────────────────────────────
Number
Item of
Number Description Price Items Total
────── ─────────── ───── ───── ─────
────────────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────────────────────────────────
Total Cost:─────────────
PLACE indicates he wants to place an order; FILL, that he wants to fill one; and
STOCK that he wants to restock merchandise.
Placing an Order
This clerk enters PLACE. The “place” portion of the program then prompts him for
the customer number:
CUSTOMER NUMBER:
CUSTOMER NUMBER: 55
Next, the program prompts him for the item number and quantity, which he types
in.
ITEM : 5613
AMT ORDERED : 2
ITEM : ──── End input; could enter more
Figure 6 shows what an invoice would look like if a customer ordered the above
item.
If the customer is new, the program prompts the clerk for customer information:
NEW CUSTOMER
CUSTOMER NAME : TWENTY TWO
ADDRESS : 22 TWENTY LANE
CITY, STATE, ZIP : TWOTOWN, TENN 22222
CUSTOMER RECORD UPDATED
ITEM :
Filling an Order
After getting her invoice from the order clerk, the customer takes it to the clerk who
fills orders. That clerk has access to the same Inventory Control Program, and
when he gets the invoice, he gets the same initial prompt:
He enters FILL, and the program prompts him for the invoice number:
Restocking Merchandise
Periodically, a clerk checks the inventory, and orders items the store is in danger of
running out of. He enters STOCK in response to the initial prompt, and the
program prompts him for the stock number:
The three people who will be using your program are the clerk who places the
order, the clerk who fills it, and the stock person who maintains the inventory. By
talking to those three people, you identify the following expanded application steps:
1. Placing the order
a. Input
Enter customer number.
If customer is new, enter customer information.
Enter information relating to the order.
b. Process
Verify that stock is available.
Put the order into a table.
Update the available stock.
c. Print
Format the invoice.
The invoice will be taken to the merchandise area, where the order will be
filled.
2. Filling the order
Ask for the order number.
Delete the order table entry.
You could choose to have the available stock updated at this point rather than
when the order was placed, but that would require keeping separate stocks table
entries for (or doing calculations for) consigned or unconsigned stock, so that the
entry clerk does not consign the same stock twice. The way it will be done, then, is
for the stock table to have unconsigned inventory, and the orders table to have the
consignments.
3. Restocking merchandise
Check for low stock.
Restock low items.
Enter information about new items.
That should do it for now. You'll begin by writing the application's building blocks
on which everything else will be based. Those building blocks (or “tools”) are:
Tables that define your application world, and that reflect its current state
Input and error checking routines
Prompting control operators
Table updating routines
STOCK REORDER
NUMBER DESCRIPTION PRICE QUANTITY LEVEL
CUSTOMER
NUMBER NAME ADDRESS CITY, STATE, ZIP
It's easy to visualize the input data as three arrays — let's call them STOCKS,
ORDERS, and CUSTOMERS. And because it's easy to think of the data as
tabular, let's assume that STOCKS, ORDERS, and CUSTOMERS are matrixes.
If STOCKS were a simple array, each character in the array would occupy one
column; if STOCKS were made up exclusively of character data, each row would
have 39 columns:
ρSTOCKS
9 39
But, if STOCKS were a nested array, you could avoid all length considerations. If
STOCKS had a structure like this:
DISPLAY STOCKS
→|||||||||||||||||||||||||||||||||||||||||
↓ →|||||||||||||||
1135 FIRST GREAT ITEM 995 118 55
||||||||||||||||
→||||||||||||
2583 A REAL WINNER 4999 89 10
|||||||||||||
→||||||||||||||||
3569 SECOND MONEYMAKER 2475 0 30
|||||||||||||||||
→|||||||
5555 WHIZBANG 2222 43 2
||||||||
→|||||||||||||||||
5613 MAIL ORDER SPECIAL 1499 225 95
||||||||||||||||||
→||||||
7777 THINGY 189 2 1
|||||||
→||||
8888 WHEEE 433 3011 100
|||||
→||||||||||||||||
9993 HIGH FLYER WIDGET 8873 240 35
|||||||||||||||||
→|||||||||||||||
9998 NONESUCH FRAMMIS 269 416 50
||||||||||||||||
ε|||||||||||||||||||||||||||||||||||||||||
you could then pick things out in natural groupings like this:
STOCKS[1;2]
FIRST GREAT ITEM
Notice that if the description of item 1135 were changed to THE BEST ITEM
THERE IS, you wouldn't have to change the expression you'd use to reference it:
STOCKS[1;2]
THE BEST ITEM THERE IS
Furthermore, nesting STOCKS gives it a more natural structure. There are nine
stock items, and five columns (not 39) in each row.
Naturally, you want the routines to be as “user friendly” as possible. How can you
make them that way? One way is to make the prompting for input as natural to the
users as possible. Furthermore, you want to make your system forgiving, so you're
going to try to anticipate user errors, notify users when they've made an error, and
give them a chance to correct it.
Let's organize your thinking into a series of actions that the input handling functions
will have to take:
Prompt for input.
Check for entry errors (as necessary, issue message and allow users to
recover).
Return the input in a form suitable for processing by table update functions.
Seems fairly straightforward. Maybe that's all the initial planning you have to do for
this part of the application. And because you're anxious to begin, why not start
coding?
In essence, this comment is the “abstract” for the function (and, you hope, prevents
the remainder of the function from looking abstract!). The abstract line shouldn't
exceed 60 to 80 characters. You may find that you can't state the function's
purpose in just one line. Stop, don't merely write a longer one! That's probably a
good indication that you're trying to do too much with that function; narrow its scope
a bit and get it down to one operation before you start to write any code.
1 The up shoe jot symbol is also known as the lamp symbol, because it “illuminates.”
As you get into the “nuts and bolts” of the code, you should also document how the
function operates. APL2 accepts comments on lines by themselves or to the right
of a statement or statement labels, so that you can liberally sprinkle the function
with a running commentary. This added effort will pay off when the function is
maintained, particularly if the maintainer isn't you.
For users of STOCK, prompting is much better. (Remember, you want to shield
them as much as possible from the programming system.)
Let's look at some ways to prompt for input. For character input, you'll use quad
quote ().
[0] Z←INPUT MSG
[1] PROMPT FOR INPUT
[2] MSG
[3] Z←
Note that input is always accepted as text. If you need to have numbers
entered, you'll either have to use the execute function () on the data or use the
evaluated input prompt mechanism. The execute function evaluates the expression
that a character vector represents. For example:
3+4
7
For the order clerks, this is probably the most natural-looking dialog.
You might think that the character vector 1135 gets assigned to Z, but that's not
all. Z is also assigned the prompt, or characters that replace the prompt. This
depends on the value of the system variable prompt replacement {PR. If {PR is
an empty vector, the prompt is assigned as is:
{PR←
A←INPUTENTER STOCK NUMBER
ENTER STOCK NUMBER: 1135
A
ENTER STOCK NUMBER: 1135
ρA
24
Otherwise, {PR contains a character (the default in a clear workspace is the blank
character), and that character replaces the prompt:
{PR←
A←INPUTENTER STOCK NUMBER
ENTER STOCK NUMBER: 1135
A
1135
ρA
24
It's worth noting that INPUT in its current form depends on the global variable of
{PR to work properly. In actual practice, it may be set to some other value in
whatever workspace this function gets used. If its value is other than a blank, this
function could start to fail in unusual situations. For this reason, it's always smart
to localize {PR to the prompting function. That way you can guarantee a proper
setting of {PR.
In the above illustration, {PR is set to a backspace character selected from the
system variable terminal control ({TC).
Because this is something that is generally useful, you use a separate function to
do it:
[0] Z←DLTMB A
[1] DELETE LEADING, TRAILING, MULTIPLE BLANKS
[2] Z← ,A,
[3] Z←(* £Z)/Z
[4] Z←1↓1↓Z
DLTMB[3] uses the find (£) primitive function to find the start of all occurrences
of two successive blanks. The resulting boolean vector is then applied as the left
operand of the / operator, producing the compress (B/) derived function (see
Figure 7).
If A is an array, and B is a boolean vector (of the same length as the last axis of A), then the expression
B/A is a derived function of the slash (/) operator. It removes an item of the last axis of A wherever a 0
appears in the corresponding position of B. This derived function is called COMPRESS. For further infor-
mation about COMPRESS, see APL2 Programming: Language Reference.
Just what you wanted — an empty array. This is a useful way to tell if the user no
longer wants to enter data.
Because you deleted excess blanks, you'll get the same effect if a row of blanks is
entered. You decide this is what you want for this application.
To get valid positive numbers, you must ensure that only the characters that can be
components of an acceptable number are allowed:
Your list of acceptable numbers excludes negative, complex, and 'E' notation
entries, such as -5, 5J0, 1E5. It includes numbers like 5 and 5.0. Including a blank
allows more than one number to be entered. If the result of a call to INPUT con-
tains any character that is not in the allowed set ('0123456789. '), then Z
becomes an empty vector. Notice that if ρZ is really zero, then an exit is taken at
NUMIN[3].
Handling Errors
NUMIN[6] can use execute () to convert Z from characters to numbers. You
want to signal an error if Z is zero at NUMIN[6]. Any strange constructions such
as 5.5.5 must also be trapped. You can accomplish both by using execute alter-
nate ({EA):
If Z contains an error, the left argument of {EA is executed, and a branch is taken
to NUMIN[8].
Employ caution in using {EA and event simulation ({ES). In particular, it is important to avoid using
{EA with the assumption that it will trap only a particular error, such as a {ES-generated one. Instead,
unexpected errors may be masked.
One way to avoid this: Before continuing, check the value of {ET to ensure that the error is one of the
errors you want to trap. Dyadic {ES is useful in this respect, as you can have it set {ET with your own
error number.
For more information about {ES and {ET, see An Introduction to APL2.
What does AGAIN do? It prompts the user to see if that user wants to retry, and,
if so, returns a 1:
A←AGAIN
RETRY(Y/N)&
Y
A
1
Here, you match (¢) the WS FULL code (1 3) against {ET, and use event
simulation ({ES) to signal the same error again if it matches. Because you are not
trapping this error with {EA, WS FULL will print at the terminal. Note that
AGAIN[1] has the same effect if it causes WS FULL itself!
)SAVE CONTINUE
followed by:
)RESET
or:
)LOAD INVENTORY
The more helpful you make this documentation, the less likely it will be that a pro-
grammer will be needed to troubleshoot the problem.
Back to AGAIN
The remainder of AGAIN uses “bare bones” prompting. Any response in which
the first character is not Y will return a zero, and the branch at NUMIN[9] will not
be taken:
By taking the AGAIN code out of NUMIN, you have reserved the option to use it
elsewhere.
If (↑TT) is neither 0 nor , you generate an error at line [5] (one of your
functions has called ASK improperly).
You now have “all you could ASK for” in the way of a general input handling facility
for your application, so you are ready to think about controlling the prompting
sequences.
CUSTOMER NUMBER
STOCK NUMBER
AMT ORDERED
The requested data is all numeric, so you create a matrix, ORDERQ, in which each
row starts with 0 (as expected by the ASK function). You do this by first entering a
nested vector:
ORDERQ←ORDERQ
DISPLAY ORDERQ
→||||||||||||||||||||
↓ →||||||||||||||
0 CUSTOMER NUMBER
|||||||||||||||
→|||||||||||
0 STOCK NUMBER
||||||||||||
→|||||||||||
0 AMT ORDERED
||||||||||||
ε||||||||||||||||||||
DISPLAY [2]ORDERQ
→||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
→|||||||||||||||||||| →||||||||||||||||| →|||||||||||||||||
→|||||||||||||| →||||||||||| →|||||||||||
0 CUSTOMER NUMBER 0 STOCK NUMBER 0 AMT ORDERED
||||||||||||||| |||||||||||| ||||||||||||
ε|||||||||||||||||||| ε||||||||||||||||| ε|||||||||||||||||
ε||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Because each will have the same effect on a one-column matrix as it does on a
vector, you have another way to do away with Step 1: Add a level of nesting to
ORDERQ and then change it back to a matrix:
ORDERQ←,[ι0][2]ORDERQ
DISPLAY ORDERQ
→||||||||||||||||||||||||
↓ →||||||||||||||||||||
→||||||||||||||
0 CUSTOMER NUMBER
|||||||||||||||
ε||||||||||||||||||||
→|||||||||||||||||
→|||||||||||
0 STOCK NUMBER
||||||||||||
ε|||||||||||||||||
→|||||||||||||||||
→|||||||||||
0 AMT ORDERED
||||||||||||
ε|||||||||||||||||
ε||||||||||||||||||||||||
A←ASK¨ORDERQ
CUSTOMER NUMBER: 55
STOCK NUMBER : 5613
AMT ORDERED : 12
A
55
5613
12
Any APL2 task can be represented as a function, so you can imagine the repetition
of any task as the repetitive application of a function:
FOO FOREVER
`
`
`
`
`
`
`
`
END OF PROCESSING
That's better.
EMPTY←,
FOOVAR
``````
[0] Z←FOO V
[1] Z←V↑FOOVAR
[2] FOOVAR←V↓FOOVAR
END OF PROCESSING
UNTIL takes two operands, the function F and the variable END. It also takes an
argument, V, which is passed to F. When the result of the call to F matches an
END condition, it exits.
DISPLAY ORD
→|||||||||||||||||||||||||
→||||||||| →||||||||
55 3569 12 7 5613 35
*||||||||| *||||||||
ε|||||||||||||||||||||||||
You would prefer, however, to have separate prompts for each of the individual
items. How can you do that?
Imagine a defined operator, EACH, which is like the primitive ¨, but quits when the
result of a function call is empty. Because a function operand of an operator can
itself be a derived function, you can say something like:
This statement, which you have “written” without leaving your armchair, executes a
loop within a loop.
Here it is in action:
Notice the parentheses in the header line of the operator definition. This tells APL2
the context in which to recognize the operator. In other words, execute the
REPEAT operator any time you encounter:
The parentheses are not needed when the operator is used, but you might include
them for clarity.
A defined operator, like a defined function, can be monadic, in which case it takes
a function or array operand on its left, or dyadic — in which case it takes a function
or array operand on its left and right. Example:
In the above example, the right operand, E, can be an array whose contents indi-
cate the stop condition (an empty vector in the monadic form).
The header line of a defined operator may contain as many as six different names with special syntactic
meaning:
[0] Z←LA (LO DOP RO) RA
The name of the above operator is DOP. The valence of DOP is defined within the parentheses; in this
case, DOP is dyadic — it takes two operands, LO and RO.
The combination of an operator and its operands comprises a derived function. Such a function can
take one or more arguments. The derived function (LO DOP RO) takes two arguments, LA and RA.
A derived function may or may not return an explicit result. In the above illustration, an explicit result, Z,
is defined.
A function that takes two arguments is ambi-valent; in any reference, the left argument may be omitted.
An operator that takes two operands can only be dyadic.
A monadic operator has a left operand only. A monadic function has a right argument only.
It is useful to keep in mind the distinction between operands and arguments and where they may appear.
By way of illustration, consider the following operator headers and their meanings.
The same, but here the operator name is LO; MOP is the operand:
(MOP LO) RA
Not valid:
LA (MOP LO)
Not valid:
(MOP LO)
For this reason, (FOO FOREVER) cannot be written with FOO as a function and FOREVER as an
operator.
It is also useful to distinguish the behavior of function and array operands. One distinction involves what
happens in the body of the operator. The statement:
LO↑RA
may be used to call function LO with the first item of argument RA. If LO is an array, however, the
expression is a take operation. The expression:
LO(↑RA)
can be used instead. In this case, an array operand is appended to the first item of the argument.
Another distinction involves the question of when operand evaluation takes place. A variable is evaluated
before it is passed as an operand of the operator. This is a particularly important distinction when the
operand is a shared variable, or when it is a niladic function with explicit result, as such a niladic function
behaves like a variable. (So operators can have niladic functions as operands? No, the niladic functions
are syntactically variables.)
Thus, using the REPEAT operator, you might expect to be able to say:
A←ASK¨ORDERQ
CUSTOMER NUMBER : 55
STOCK NUMBER : ────────────User presses ENTER only
AMT ORDERED : 12
A
55
12
ρA
3 1
An empty input line should indicate that the user wants to discontinue the current
series of prompts. In such a case, the function that is being repeated should exit to
the next level. You can achieve this effect by creating an operator that exits when
it detects an empty array. For this operator, EACH is as good a name as any.
You have it return a vector:
The REPEAT operator can now test for completion by comparing the number of
items in the result to the number of items in the right argument. It will print a
warning message, drop the items for that prompting sequence, and repeat. It will
exit when an empty response is given to the first prompt. Example:
A←ASK EACH REPEAT ORDERQ
CUSTOMER NUMBER : 55
STOCK NUMBER : 3569
AMT ORDERED : 12
CUSTOMER NUMBER : 35
STOCK NUMBER : ────────User presses ENTER key
***DATA NOT SAVED ────────REPEAT prints a message
CUSTOMER NUMBER : 55 ────────User is prompted again from the top
STOCK NUMBER : 1135
AMT ORDERED : 12
CUSTOMER NUMBER : ────────Now user is finished
DISPLAY A
→||||||||||||||||||||||||||
→||||||||| →|||||||||
55 3569 12 55 1135 12
*||||||||| *|||||||||
ε||||||||||||||||||||||||||
Write Them Now, Use Them Forever: You will be able to use the EACH and
REPEAT operators, and others like them, in many places. It's worth the effort to
develop them now. You'll write them so that they can be read and used by others,
in other contexts.
REPEAT Line-by-Line
Here is the complete REPEAT operator:
The left operand of REPEAT must be a monadic function. In APL2, a single func-
tion argument may be a nested array that contains all parameters needed by the
function, so you do not need a dyadic form (though sometimes it is handy to have
one).
assures that the left operand is a function. If not, a program error is generated in
the function that called (F REPEAT).
REPEAT[5]:
REPEAT[7]:
REPEAT[8]:
The left argument of ERR is the text of a message; the right argument is a condi-
tion (a boolean 0 or 1). If COND is 1, and MSG is not empty, the message is
printed. The explicit result of ERR is always COND, and is used to control
branching in the calling function.
The warning message printed with the ERR subroutine is contained in a global vari-
able:
WARNING
***DATA NOT SAVED
For some uses, REPEAT[8] may be omitted, or its effect negated by setting
WARNING to an empty vector, as ERR will then bypass message printing.
Because it is easier to prove the correctness of programs when the effect they
produce is dependent only on the arguments passed directly to them, you may
prefer to make WARNING an argument (see Figure 10 on page 50).
Any function defined with two arguments may be called either monadically or dyadically. Thus, an attempt
to use the left argument may result in a VALUE ERROR.
To avoid this, it is advisable to check for the existence of the left argument:
[0] Z←L F R
[1] {ES(2≠{NCL)/MISSING LEFT ARGUMENT
`
`
`
`
If missing, you can print an error message, as above, assign a default, or execute a function or piece of
code that uses the right argument only.
If the ambi-valent function is a derived function, then this check must be done not only in any ambi-valent
functions that may be operands of the operator, but in the body of the operator definition itself, because a
value error could occur in an attempt to call a function with a missing left argument.
EACH Line-by-Line
Here is the complete EACH operator:
As with REPEAT, you must ensure that F is a function. If it were not, the state-
ment at EACH[9] would be a pick of R with a two-item left argument, and might
or might not work. You could have said F(IR) instead, which would give some
sort of an answer with an array, but you don't want to include this possibility in the
domain of your definition of EACH.
EACH[5] sets an empty initial result and, at the next line, you decide to merely
ignore empty right arguments.
In the next line you create an index variable, I. {IO is used to ensure index
independence (you want EACH to be used in other workspaces).
The label at EACH[9] signals the beginning of the loop. It will end if one of two
conditions is met:
The result of a call to function F is empty.
All the items of R have been processed.
Note that I is not incremented and compared on the same line. If a failure
occurred at EACH[12], you would be able to continue with the knowledge that I
will not be incremented twice. This sort of programming practice eases function
debugging.
The columns of your tables are identified by the arrays you have set up for
prompting:
→||||||||||||||||||||||||||||||||||||
→|||||||||||||||
1135 FIRST GREAT ITEM 995 118 50
||||||||||||||||
ε||||||||||||||||||||||||||||||||||||
How do you store this in STOCKS? What you want to do is to replace a row in
STOCKS, in the position where the value of the first item is the same. If such a
row doesn't currently exist, you want to add a row at the end.
What if STOCKS contained nothing at all? Indeed, you haven't created it yet. Can
you add something to nothing? Yes, if you create STOCKS as an empty array
with no rows and 5 columns:
STOCKS←0 5ρ0 0 0 0
Initially, you may create STOCKS and your other tables in your workspace as
global variables. This requires the user to save all the data. Before going into
production, you'll have to use one of the auxiliary processors that APL2 provides for
file access, such as AP 127. AP 127 provides access to data base management
systems that support the Structured Query Language (SQL).
When it's time to make STOCKS an SQL table, you will use an SQL CREATE
statement. If data already exists in your table, you add a VALUES clause, and
pass all existing rows as the initial values.
See Chapter 12, “Using SQL Tables” on page 89, for a sample CREATE state-
ment. For a more detailed discussion of SQL tables, see APL2 Programming:
Notice that the empty array assigned above to STOCKS mirrors the data types that
the clerks will be entering — a number in the first column, characters in the second,
and so on. This isn't a requirement; you could have created an empty STOCKS by
assigning it 0 5ρ or 0 5ρι0. You do need to give STOCKS five columns;
otherwise, you can't add a five-column row to it.
PUT Comes in Two Flavors: You have already decided to have two flavors of
PUT: one for now, and one for when you add SQL data base access. So that you
don't have to change all the references to PUT, you use it as a shell that calls
PUTW, the workspace version of PUT, from PUT. Later, you will change the
PUTW reference to a PUTSQL reference.
The PUTW Function: Okay, so you still have to code PUTW. You write it so that
you can both add and replace rows:
PUTW[2] makes sure the requested table NAME is one the application knows
about. TABLES is merely a list of these tables:
The error handling at PUTW[2] treats the error as a user error, not a program
error. By doing this, you have conceptually externalized PUT, leaving open the
possibility of direct use by a sophisticated user. An alternative is to use {ES,
which would force suspension in the calling function.
TAB←NAME
TAB
1135 FIRST GREAT ITEM 995 118 55
9993 HIGH FLYER WIDGET 8873 240 35
3569 SECOND MONEYMAKER 2475 0 30
5613 MAIL ORDER SPECIAL 1499 225 95
2583 A REAL WINNER 4999 89 10
9998 NONESUCH FRAMMIS 269 440 50
You then find where the first column of NEW, the matrix of updates, matches the
first column in TAB, by creating a boolean vector, OLD:
OLD used at PUTW[5] to find the indices of occurrence of the old rows in TAB.
These indices are then used in PUTW[6] to update TAB with the corresponding
rows of NEW:
((TAB[;1]εOLD/NEW[;1])TAB))←OLDNEW
PUTW[7] appends those rows of NEW whose key column does not match
existing columns in TAB.
At PUTW[8], you assign TAB back to the original table whose name is in NAME:
STOCKS
1135 FIRST GREAT ITEM 995 118 55
9993 HIGH FLYER WIDGET 8873 240 35
3569 SECOND MONEYMAKER 2475 10 30
5613 MAIL ORDER SPECIAL 1499 225 95
2583 A REAL WINNER 4999 89 10
9998 NONESUCH FRAMMIS 269 416 50
7777 THINGY 189 2 1
8888 WHEEE 433 3011 100
5555 WHIZBANG 2222 43 2
For a discussion of PUTSQL, see Chapter 12, “Using SQL Tables” on page 89.
As with PUT, you will want to convert to SQL, so GET looks like this:
GETW obtains a local copy of the table, in the same way that PUT does. Then
GETW[5] creates a boolean vector, B, which represents the occurrences of N in
the first column.
GETW[6] prints a message if any of these items are not found, but do not exit;
the →0ρ applies to the result of the ERR function, and merely causes control to be
passed to the next line. GETW[7] exits if B is all zero.
Note that GETW[5] through GETW[7] are needed only so that a message can
be printed for the “not found” items. In writing code for interactive use by others, it
is your responsibility to be reasonably explicit about the occurrences of unexpected
conditions.
STOCKS GETι0
STOCKS GET 1
ITEM(S) 1 NOT FOUND
[0] UPDATE;WHAT;I;MSG
[1] UPDATE AN INVENTORY TABLE
[2] →(0=ρWHAT←INPUT CHOOSE,3OR,1TABLES)/0
[3] I←TABLESιWHAT TABLE INDEX
[4] →(REQUEST NOT RECOGNIZED ERR I>ρTABLES)/0
[5] MSG←IPROMPTS SELECT PROMPT TABLE
[6] A←ASK EACH REPEAT MSG PROMPT FOR INPUT
[7] →(0=ρA)/0 EXIT IF EMPTY RESPONSE
[8] WHAT PUT A PUT ROWS A TO TABLE WHAT
At UPDATE[2], the user selects one of the tables in the global variable
TABLES:
TABLES
STOCKS CUSTOMERS ORDERS
UPDATE[3] obtains an index, and [4] checks if it is valid. The index is then
used at UPDATE[5] to select a prompting matrix from the global variable
PROMPTS:
PROMPTS
STOCKQ CUSTQ ORDERQ
It is assigned to MSG.
Changing an Address with UPDATE: UPDATE gives you some basic capabili-
ties that can be used to handle both specified and unspecified user needs. For
example, you will need to make customer name and address changes. You can
handle that with a call to UPDATE:
UPDATE
CHOOSE STOCKS CUSTOMERS OR ORDERS : CUSTOMERS
CUSTOMER NUMBER : 55
CUSTOMER NAME : NEWNAME
ADDRESS : NEW ADDRESS
CITY, STATE, ZIP : NEWZIP
CUSTOMER NUMBER :
CUSTOMERS
7 CITY TRADERS INC 41 POSTAGE ROAD RIMELA, NY 12345
55 NEWNAME NEW ADDRESS NEWZIP
312 MANTUP SALES CORP RURALIA FARMS, RFD 2 SUBURBIA, WIS 00000
UPDATE can be added to your user menu, as shown in Chapter 9, “Creating the
PLACE Function” on page 63.
DELETE ensures that the table name is valid, and that an entry exists. Then
DELETE[6] removes the first row whose first column is N. In the call from
FILL, N will be the invoice number. DELETE[7] assigns the temporary table
TAB back to the global table, in this case, ORDERS.
You start by creating a variable that describes the items on your menu:
You will use ITEMS in more than one way. First it's used for the prompting in
MENU:
[0] MENU
[1] SELECT AN ITEM
[2] L1:
[3] WHAT←INPUT CHOOSE,3OR,1ITEMS
[4] →(0=ρWHAT)/0 TERMINATE IF EMPTY RESPONSE
[5] →(REQUEST NOT RECOGNIZED ERR*(WHAT)εITEMS)/L1
[6] {←{EM[1;] {EA WHAT
[7] →L1 ASK AGAIN
MENU
CHOOSE PLACE FILL OR STOCK :
By doing it this way, you can add items to your menu just by appending to ITEMS,
as in:
ITEMS←ITEMS,UPDATE
ρITEMS
4
MENU
CHOOSE PLACE FILL STOCK OR UPDATE :
It's important to distinguish the effects produced by various ways of appending arrays to arrays. In partic-
ular, note the differences between the following expressions:
ITEMS,CHECK
ITEMS CHECK
ITEMS,CHECK
ITEMS (CHECK)
In the first expression, CHECK is enclosed () to produce a scalar, which is then catenated to a 3-item
vector, producing a 4-item vector of vectors:
The third expression catenates a 5-item vector to a 3-item vector, producing an 8-item vector:
In the last expression, a 2-item vector is produced, in which the second item is a scalar containing a
5-item vector:
DISPLAY ITEMS (CHECK) A 2 ITEM VECTOR
→|||||||||||||||||||||||||||||||||||||||
→||||||||||||||||||||||| |||||||||
→|||| →||| →|||| →||||
PLACE FILL STOCK CHECK
||||| |||| ||||| |||||
ε||||||||||||||||||||||| ε||||||||
ε|||||||||||||||||||||||||||||||||||||||
The variable WHAT contains the user's menu selection. It is enclosed so that the
entire word is treated as a single scalar item:
WHAT←PLACE
DISPLAY WHAT
|||||||||
→||||
PLACE
|||||
ε||||||||
If you did not enclose WHAT, you would be comparing each of the letters contained
in WHAT:
WHATεITEMS
0 0 0 0 0
After the MENU item is selected, you want to execute the appropriate routine. This
is easy if the name of the function is the same as the prompt.
Of course, you want to trap errors and give the user a chance to try again:
MENU[6] executes the function named in WHAT, and prints the first line of the
interpreter event message ({EM) if there is a failure.
MENU[7] (the last line of MENU) starts the menu prompting sequence over
again.
You sketched out all these steps before you started the building blocks. When you
have PLACE running, you'll have made use of all the blocks.
[0] PLACE;ORD;CUST;ORDQ
[1] PLACE AN ORDER
[2] →(0=ρCUST←ASK↑ORDERQ)/0 GET CUSTOMER NUMBER
Example:
CUST←ASK↑ORDERQ
CUSTOMER NUMBER : 55
CUST
55
Now the customer number can be compared to the customer list. If it's not there,
the clerk can enter new customer statistics:
NEWCUST will require a prompting matrix. You include the customer number, so
that it corresponds with the columns of the CUSTOMERS table:
CUSTQ
0 CUSTOMER NUMBER
CUSTOMER NAME
ADDRESS
CITY, STATE, ZIP
Line [4] of NEWCUST uses drop with axis to dispose of the first row of CUSTQ
before calling (ASK EACH). It exits if there is an empty response.
NEWCUST[6] calls PUT to update tables. The left argument of PUT is a table
name. The right argument is the data to be added. It includes the key field that
identifies the row of the table the data belongs in. For the customer table, the key
is the customer number, which you have placed in CUST.
The Invoice Number: Each order form will have a unique invoice number
assigned to it. You use a global variable, INVO, to contain the current invoice
number.
ACTORD subtracts the number of parts ordered from the number in stock. In addi-
tion, it takes care of the case where there is not enough inventory to fill the order.
In this case, the clerk is notified and the amount ordered is reduced as appropriate.
At ACTORD[4], ACTORD calls the GET function to get the stock record for the
order it is processing.
Reducing the Order: ACTORD[5] calculates the new available stock after
consignment of stock to this order. If the order quantity is greater than or equal to
the stock available, the new available stock is zero. In this case, the order is
reduced in ACTORD[7] to what was available.
When ACTORD returns to PLACE, some of the processed orders have been
reduced to zero, either because of a GET failure or because no stock was avail-
able. For example, if stock item 3569 has 0 units available, and item 2222 is not in
stock, the following could occur:
PLACE
CUSTOMER NUMBER : 55
STOCK NUMBER : 3569
AMT ORDERED : 10
STOCK NUMBER : 2222
AMT ORDERED : 20
STOCK NUMBER :
ORDER FOR STOCK 3569 REDUCED TO 0
ITEM(S) 2222 NOT FOUND
Handling Empty Orders: PLACE[8] deletes the zeroed orders from ORD, the
vector of order vectors. If they are all zero, we exit from PLACE:
The clerk may prefer to be given the opportunity to correct an order that is in error
before the invoice is printed. If this is the case, you can add this capability later,
perhaps with an optional call to UPDATE.
DISPLAY ORD
→||||||||||||||||||||||||||||||||||||||||||||||||||
→||||||||||||| →|||||||||||| →|||||||||||||
121 55 3569 12 121 7 5613 35 121 312 1135 2
*||||||||||||| *|||||||||||| *|||||||||||||
ε||||||||||||||||||||||||||||||||||||||||||||||||||
It is a 3-item vector of 4-item vectors. The second item of this array can be selected using pick ():
2ORD
121 7 5613 35
2 4ORD
35
To select the second item of each of the items, you use ¨:
2¨ORD
55 7 312
2 3 4¨ORD
55 5613 2
What if you want to select the second and third items of ORD using pick? You can resort to the
chipmunk idiom (¨):
2 3¨ORD
121 7 5613 35 121 312 1135 2
The same method can be used to pick at greater depth. The following picks the third item of each of the
second and third vectors:
(2 3)(3 3)¨ORD
5613 1135
¨
you'll see how the chipmunk idiom got its name.
CUST←ASK ↑ORDERQ
CUSTOMER NUMBER : 55
INVO
179
DISPLAY ORD
→|||||||||||||||||||
→||||| →||||||
1135 2 3569 12
*||||| *||||||
ε|||||||||||||||||||
DISPLAY CUST,¨ORD
→|||||||||||||||||||||||||
→|||||||| →|||||||||
55 1135 2 55 3569 12
*|||||||| *|||||||||
ε|||||||||||||||||||||||||
DISPLAY INVO,¨CUST,¨ORD
→|||||||||||||||||||||||||||||||||
→|||||||||||| →|||||||||||||
179 55 1135 2 179 55 3569 12
*|||||||||||| *|||||||||||||
ε|||||||||||||||||||||||||||||||||
The result is a vector of order vectors that can be used to update the order table.
DISPLAY INVO,CUST,ORD
→||||||||||||||||||||||||||
→||||| →||||||
179 55 1135 2 3569 12
*||||| *||||||
ε||||||||||||||||||||||||||
The information from INVOICE is kept logically separate from the report format-
ting so that you can use it, if needed, in other ways. FORMAT and INVOICE
are discussed in Chapter 10, “Formatting the Invoice: The INVOICE and FORMAT
Routines” on page 72.
[0] PLACE;ORD;CUST;ORDQ
[1] PLACE AN ORDER
[2] →(0=ρCUST←ASK↑ORDERQ)/0 GET CUSTOMER NUMBER
[3] NEWCUST CUST ADD NEW CUSTOMER
[4] ORDQ←1↓[1]ORDERQ ITEMS TO PROMPT FOR
[5] →(0=ρORD←ASK EACH REPEAT ORDQ)/0
[6] ACTORD FINDS ACTUAL ORDERS AND UPDATES STOCKS
[7] ORD←ACTORD EACH ORD
[8] →(0=ρORD←(ε0≠2¨ORD)/ORD)/0 DELETE EMPTY ORDERS
[9] INVO←INVO+1 NEW INVOICE NUMBER
[10] ORD←INVO,¨CUST,¨ORD APPEND TO ENTRIES
[11] ORDERS PUT ORD UPDATE ORDERS
[12] FORMAT INVOICE↑↑ORD DISPLAY INVOICE
STOCKS
1135 FIRST GREAT ITEM 995 118 55
9993 HIGH FLYER WIDGET 8873 240 35
3569 SECOND MONEYMAKER 2475 10 30
5613 MAIL ORDER SPECIAL 1499 195 95
2583 A REAL WINNER 4999 89 10
9998 NONESUCH FRAMMIS 269 416 50
7777 THINGY 189 2 1
8888 WHEEE 433 3011 100
5555 WHIZBANG 2222 43 2
ORDERS
123 55 3569 5
123 55 1135 12
123 55 2583 4
131 55 3569 10
135 312 9998 12
136 7 1135 2
PLACE
CUSTOMER NUMBER : 55
STOCK NUMBER : 3569
AMT ORDERED : 12
STOCK NUMBER : 55
AMT ORDERED :
***DATA NOT SAVED
STOCK NUMBER : 5613
AMT ORDERED : 12
STOCK NUMBER :
ORDER FOR STOCK 3569 REDUCED TO 10
Begin Invoice
42738
End Invoice
STOCKS
1135 FIRST GREAT ITEM 995 118 55
9993 HIGH FLYER WIDGET 8873 240 35
3569 SECOND MONEYMAKER 2475 0 30 ────Changed
5613 MAIL ORDER SPECIAL 1499 183 95 ────Changed
2583 A REAL WINNER 4999 89 10
9998 NONESUCH FRAMMIS 269 416 50
7777 THINGY 189 2 1
8888 WHEEE 433 3011 100
5555 WHIZBANG 2222 43 2
ORDERS
123 55 3569 5
123 55 1135 12
123 55 2583 4
131 55 3569 10
135 312 9998 12
136 7 1135 2
141 55 3569 10 ────Added
141 55 5613 12 ────Added
123 55 3569 5
123 55 1135 12
123 55 2583 4
The columns represent the invoice number, customer number, item, and quantity
ordered.
You want to produce an invoice for these orders that contains the following:
The invoice number
The customer number
A single item with the following stock information for each order:
– Stock number
– Description
– Price per item
– Quantity ordered
– Price times quantity
Well, you know where all the data is. All you need is an invoice number as an
argument, and you can get it:
INVOICE[5] gets all stock records for the orders covered by the invoice. In the
above example, ORD[3] is 3569 1135 2583.
INVOICE[6] creates records with the stock items, descriptions, and price, fol-
lowed by the quantity. The expression:
3↑[2]STO
takes the first three columns of STO. INVOICE[7] appends the cost.
You do not need to have INVOICE handle more than one invoice number: A list
of valid invoice numbers could be handled with the primitive operator each (¨).
INVOICE¨123 131
123 55 1135 FIRST GREAT ITEM 995 5 4975
3569 SECOND MONEYMAKER 2475 12 297
2583 A REAL WINNER 4999 4 19996
Well, what are you going to do with the information now that you've got it? Pass it
to a report formatting function. See below.
INV
123 55 3569 SECOND MONEYMAKER 2475 5 12375
1135 FIRST GREAT ITEM 995 12 1194
2583 A REAL WINNER 4999 4 11996
↑INV
123
2INV
55
Chapter 10. Formatting the Invoice: The INVOICE and FORMAT Routines 73
3INV
3569 SECOND MONEYMAKER 2475 5 12375
1135 FIRST GREAT ITEM 995 12 1194
2583 A REAL WINNER 4999 4 11996
Now, to fit the preprinted order form shown in Figure 5 on page 24, you need to
format INV and add some information to it, so that it looks like this:
ADDRESS OF 123
THIS
CUSTOMER TODAYS DATE
TOTAL COST
In fact, thinking of the invoice this way suggests the steps you might follow in han-
dling the formatting:
1. Get the invoice number and date and put that information in the top right.
2. Get the customer address and put it in the top left.
3. Get the part number information and put it in the middle.
4. Compute the total cost.
5. Put the total below the part-number information.
6. Put everything together.
Today's Date: This is done with the niladic defined function DATE:
DATE sets the index origin ({IO) to 1, so that it can be used in any workspace.
The system variable {TS is assigned to Z at FORMAT[2], so that its value can
be referenced more than once (otherwise, you could get the wrong month at mid-
night of the last day of the month). {TS is a seven-item numeric vector that
contains the current time and date.
The first three items are the year, month, and day:
{TS[2 3 1]
5 17 1984
Everything to the left of Z[3 1] puts the day and year into the format you want:
This statement uses format by example () to display a comma between the day
and year. For detailed discussions of format by example, see APL2 Programming:
Language Reference and An Introduction to APL2.
Chapter 10. Formatting the Invoice: The INVOICE and FORMAT Routines 75
MONTHS is a matrix that lists the months of the year:
MONTHS
JANUARY
FEBRUARY
MARCH
APRIL
MAY
JUNE
JULY
AUGUST
SEPTEMBER
OCTOBER
NOVEMBER
DECEMBER
ρMONTHS
12 9
MONTHS[2{TS;]
MAY
The Invoice Number: The first item in INV is the invoice number. The
expression ↑INV changes it to a three-item character vector:
ρ↑INV
3
You want the order number and date to look like this in the formatted invoice:
123
MAY 7, 1984
In other words, you want two blank rows between the order number and the date:
(↑INV) DATE
123
You also want some blank space to the left of the block:
TOPR←6↑[1]18↑¨(↑INV) DATE
TOPR
123
Because the take is positive (6↑[1]), the filling is done at the end of the pertinent
axis. In this case, two blank rows are added to the bottom.
In ADDRESS[4], B is used to select the needed rows for all columns but the
first.
You have written ADDRESS to return more than one address, in anticipation of
requirements such as creating mailing labels. For this reason, the result of
ADDRESS is a matrix:
DISPLAY ADDRESS 55
→||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
↓ →|||||||||||||| →||||||||||||||||| →|||||||||||||||||||||||||
| |MAIL HOUSE LTD| |711 RAMBLERS LANE | |ISLAND CITY, S DAK 54321| |
| ||||||||||||||| |||||||||||||||||| ||||||||||||||||||||||||||
ε||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Chapter 10. Formatting the Invoice: The INVOICE and FORMAT Routines 77
If you disclose it, a rank 3 array is produced:
DISPLAY ADDRESS 55
→|||||||||||||||||||||||||
↓↓MAIL HOUSE LTD
||711 RAMBLERS LANE |
ISLAND CITY, S DAK 54321
||||||||||||||||||||||||||
DISPLAY ,ADDRESS 55
→|||||||||||||||||||||||||
↓MAIL HOUSE LTD
|711 RAMBLERS LANE |
ISLAND CITY, S DAK 54321
||||||||||||||||||||||||||
ρ,ADDRESS 55
3 26
Finishing the Top Left: Again, you'd like some space around the result:
ρTOPL
6 30
3INV
3569 SECOND MONEYMAKER 2475 5 12375
1135 FIRST GREAT ITEM 995 12 1194
2583 A REAL WINNER 4999 4 11996
You use format by specification () to format the result. Each pair of numbers to
the left of tells APL2 how many positions to leave for the column and how many
decimal place positions to leave:
BODY←4 0 23 0 6 2 3 0 9 23INV
BODY
3569 SECOND MONEYMAKER 2475 5 12375
1135 FIRST GREAT ITEM 995 12 11940
2583 A REAL WINNER 4999 4 11996
(3INV)[;5]
12375 1194 19996
+/(3INV)[;5]
44311
You now want to put the total below the part-order information. To do this, you
format the total:
8 2+/(3INV)[;5]
44311
and catenate it, as a new row, to the part number information. But because you
want the total to line up underneath the price column, you've got to do some over-
taking first:
TOTAL←48↑8 2+/(3INV)[;5]
Now you can append it to the bottom, with a blank line inserted:
BODY←BODY,[1] TOTAL
BODY
3569 SECOND MONEYMAKER 2475 5 12375
1135 FIRST GREAT ITEM 995 12 11940
2583 A REAL WINNER 4999 4 19996
44311
Chapter 10. Formatting the Invoice: The INVOICE and FORMAT Routines 79
FORM← ,[1](TOPL,TOPR),[1]BODY
FORM
44311
54671
24750
[0] FILL;N
[1] DELETES AN ORDER WHEN IT IS FILLED
[2] N←NUMIN INVOICE NUMBER
[3] →(0=ρN)/0 EXIT IF NO INPUT
[4] ORDERS DELETE↑N
Only the first number entered is handled on FILL[4]. You could have chosen
instead to modify your input routines so that you can specify how many items are
allowed. If you did this with an optional left argument and made the default one
item, your existing functions would be unaffected. However, you would have to
account for ambi-valence in all your prompting functions and operators (see
Figure 10 on page 50).
ORDERS
131 55 3569 10
135 312 9998 12
136 7 1135 2
179 55 1135 12
FILL
INVOICE NUMBER: 131
ORDERS
135 312 9998 12
136 7 1135 2
179 55 1135 12
FILL
INVOICE NUMBER: 123
NO ENTRY FOR 123
You have STOCK call separate functions for each of its tasks:
[0] STOCK
[1] UPDATE THE STOCKS TABLE
[2] CHECK ITEMS TO CHECK
[3] RESTOCK LIST LOW ITEMS; UPDATE
[4] NEW NEW ITEMS
CHECK
STOCK ITEMS TO CHECK: 1135 3569 2222
ITEM(S) 2222 NOT FOUND
STOCK NUMBER DESCRIPTION PRICE INVENTORY REORDER LEVEL
1135 FIRST GREAT ITEM 995 118 55
3569 SECOND MONEYMAKER 2475 10 30
[0] Z←CHECK;N
[1] RETURN STOCK REPORT FOR SELECTED ITEM
[2] N←NUMIN STOCK ITEMS TO CHECK
[3] →(0=ρZ←N)/0
[4] Z←STOCKS GET N
[5] →(0=ρZ)/0
[6] Z←(,2¨STOCKQ),[1]Z
The report headings come directly from the prompting matrix, STOCKQ.
CHECK[6] picks the second item of each item, ravels the result for conformity,
and catenates it to the matrix of items selected from the table. The following
example demonstrates this, using the entire STOCKS table:
DISPLAY ,2¨STOCKQ
→|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
→||||||||||| →||||||||||| →||||| →||||||||| →|||||||||||||
STOCK NUMBER DESCRIPTION PRICE INVENTORY REORDER LEVEL
|||||||||||| |||||||||||| |||||| |||||||||| ||||||||||||||
ε|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(,2¨STOCKQ),[1]STOCKS
ITEM DESCRIPTION PRICE INVENTORY REORDER LEVEL
1135 FIRST GREAT ITEM 995 118 55
9993 HIGH FLYER WIDGET 8873 240 35
3569 SECOND MONEYMAKER 2475 10 30
5613 MAIL ORDER SPECIAL 1499 225 95
2583 A REAL WINNER 4999 89 10
9998 NONESUCH FRAMMIS 269 416 50
7777 THINGY 189 2 1
8888 WHEEE 433 3011 100
5555 WHIZBANG 2222 43 2
RESTOCK prints a list of low items, prompts the user for new information, and
then updates STOCKS based on the user's input. RESTOCK also displays infor-
mation about the part.
The reply to INCREMENT is added to the current number on hand for the stock
item.
CHECK
STOCK ITEMS TO CHECK: 1135 3569
ITEM DESCRIPTION PRICE INVENTORY REORDER LEVEL
1135 FIRST GREAT ITEM 995 148 55
3569 SECOND MONEYMAKER 2475 35 30
Now the user can do a check only by selecting STOCK from the menu. You make
a note to add it to the menu if it becomes a frequent need. Or perhaps RESTOCK
should print the modified lines.
You also make a note that it may be necessary to generate an order form for low
items (in the above example, 30 more units of item 3569 had already been ordered
and had just arrived). You will talk to the stock clerk again, to see if you can auto-
mate the order process.
As a developer of APL2 code, you do not pretend omniscience with regard to the
needs of your application. Rather, you develop your application so that it is easily
modifiable, with full awareness of the fact that your users may request extensive
changes after they start to use the application and get the “feel” of what it can do,
and of how they want to interact with it. You are confident that you have the ability
to meet the challenge of further user demands, because you have talked to your
users, taken a modular tool-oriented approach, and used tables in lieu of coding
detail.
[0] RESTOCK;TYPES;MSG;LOW;A
[1] UPDATES THE STOCK TABLE
[2] LOW←(≯/STOCKS[;4 5])/STOCKS[;1]
[3] {←LOW ITEMS: LOW
[4] MSG←,(0,STOCK NUMBER)(0,INCREMENT)
[5] A←RESTOCKIN REPEAT MSG
[6] →(0=ρA)/0
[7] STOCKSPUT,¨↑¨A UPDATE STOCK TABLE
At lines [2] and [3], all items for which the available stock does not exceed the
reorder level are listed as low.
RESTOCK[4] creates a prompting vector of depth 3, for use with the REPEAT
operator.
MSG is a length 1 vector so that the result of ASK will pass the shape check in
REPEAT. In RESTOCKIN[2], you pass the first message to ASK by selecting
the first item of MSG:
DISPLAY 1 1MSG
→|||||||||||||||||
→|||||||||||
0 STOCK NUMBER
||||||||||||
ε|||||||||||||||||
RESTOCKIN[6] shows the current row of STOCKS for the requested item.
RESTOCKIN[9] updates the current line, and line [10] returns it as a length 1
vector.
DISPLAY A
→|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
→|||||||||||||||||||||||||||| →|||||||||||||||||||||||||||||||
→|||||||||||||||||||||||| →|||||||||||||||||||||||||||
↓ →|||||| ↓ →||||
7777 THINGY 189 9 1 8888 WHEEE 433 3019 100
||||||| |||||
ε|||||||||||||||||||||||| ε|||||||||||||||||||||||||||
ε|||||||||||||||||||||||||||| ε|||||||||||||||||||||||||||||||
ε|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
The expression at RESTOCK[6], (,¨↑¨A) strips away the outer layer and
changes the matrices to vectors:
DISPLAY ,¨↑¨A
→|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
→|||||||||||||||||||||||| →|||||||||||||||||||||||||||
→|||||| →||||
7777 THINGY 189 9 1 8888 WHEEE 433 3019 100
||||||| |||||
ε|||||||||||||||||||||||| ε|||||||||||||||||||||||||||
ε|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
What If the User Makes a Mistake?: You will have to have an update capability
for STOCKS that allows an entire row to be reentered for an existing item. You
have what you need to do this for any table. You called it UPDATE, and you
wrote it when you finished writing PUT and GET.
[0] NEW;STO
[1] ADD ITEMS TO STOCKS TABLE
[2] {←NEW STOCK ITEMS:
[3] STO←ASK EACH REPEAT STOCKQ
[4] →(0=ρSTO)/0
[5] STOCKS PUT STO
STOCK
STOCK ITEMS TO CHECK: 3569
STOCK NUMBER DESCRIPTION PRICE INVENTORY REORDER LEVEL
3569 SECOND MONEYMAKER 2475 10 30
LOW ITEMS: 3569
STOCK NUMBER: 3569
NOW READS: 3569 SECOND MONEYMAKER 2475 10 30
INCREMENT: 20
STOCK NUMBER:
NEW STOCK ITEMS:
STOCK NUMBER : 2222
DESCRIPTION : TUTUS
PRICE : 2222
INVENTORY : 22
REORDER LEVEL : 2
STOCK NUMBER :
STOCKS
1135 FIRST GREAT ITEM 995 118 55
9993 HIGH FLYER WIDGET 8873 240 35
3569 SECOND MONEYMAKER 2475 30 30 ──────Updated
5613 MAIL ORDER SPECIAL 1499 195 95
2583 A REAL WINNER 4999 89 10
9998 NONESUCH FRAMMIS 269 416 50
7777 THINGY 189 2 1
8888 WHEEE 433 3011 100
5555 WHIZBANG 2222 43 2
2222 TUTUS 2222 22 2
You can use SQL tables through the APL2 auxiliary processor, AP 127. For infor-
mation on how to use AP 127, see:
APL2 Programming: Using Structured Query Language (SQL)
You will also have to know how to obtain table access authorization. For informa-
tion about that, see:
IBM DATABASE 2: Data Base Planning and Administration Guide
and:
SQL/Data System Planning and Administration
CSTOCKS←IN
CREATE TABLE STOCKS
(ITEM SMALLINT,
DESCRIPTION CHAR(20),
PRICE DECIMAL(6,2),
QUANTITY SMALLINT,
REORDER SMALLINT)
The CREATE statement can be scheduled using the SQL workspace function SQL:
SQL CSTOCKS
0 0 0 0 0 ────Return code shows success
TABLES
STOCKS CUSTOMERS ORDERS
To allow statement selection, you can write a matrix of SQL UPDATE statements:
SQLUPDATES←IN
UPDATE STOCKS
SET DESCRIPTION=:2,PRICE=:3,QUANTITY=:4,REORDER=:5
WHERE ITEM=:1
SQLUPDATES←SQLUPDATES,IN
UPDATE CUSTOMERS
SET NAME=:2,ADDRESS=:3,ZIP=:4
WHERE CUST=:1
SQLUPDATES←SQLUPDATES,IN
UPDATE ORDERS
SET CUST=:2,ITEM=:3,AMOUNT=:4
WHERE INVOICE=:1
SQLUPDATES←,[ι0]SQLUPDATES
DISPLAY SQLUPDATES
→|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
↓ →|||||||||||||||||||||||||||||||||||||||||||||||||||||
↓ UPDATE STOCKS
SET DESCRIPTION=:2,PRICE=:3,QUANTITY=:4,REORDER=:5
WHERE ITEM=:1
||||||||||||||||||||||||||||||||||||||||||||||||||||||
→||||||||||||||||||||||||||||||
↓ UPDATE CUSTOMERS
SET NAME=:2,ADDRESS=:3,ZIP=:4
WHERE CUST=:1
|||||||||||||||||||||||||||||||
→||||||||||||||||||||||||||||||
↓ UPDATE ORDERS
SET CUST=:2,ITEM=:3,AMOUNT=:4
WHERE INVOICE=:1
|||||||||||||||||||||||||||||||
ε|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SQLINSERTS
INSERT INTO STOCKS
(ITEM,DESCRIPTION,PRICE,QUANTITY,REORDER)
VALUES(:1,:2,:3,:4,:5)
You can select the appropriate statement from a statement table by finding the
position of the table name in TABLES:
(TABLESιCUSTOMERS),SQLINSERTS
These statements can then be passed to AP 127. The easiest way to do this is by
using the cover functions in the APL2 distributed workspace, SQL. You write
PUTSQL with the assumption that you have copied SQLGROUP from the SQL
workspace into your INVENTORY workspace.
After ensuring that the table name is a known one, you obtain the corresponding
index, T.
First you select an update statement and pass it to the PREP function along with
a statement name, UP, which you will use to refer to this statement:
PREP prepares the statement for a subsequent call. The first item of the result is
the return code; you assign it to E.
To handle the values, you call another function recursively, using EACH:
The SQLUPIN function will schedule SQL calls to update or insert the row of
values passed to it:
If the second item of E is 1 upon return from the update, the record was not found,
and must be inserted. This decision is made at SQLUPIN[5].
SQLSELECTS
SELECT * FROM STOCKS
WHERE ITEM = :1
The * indicates that all columns are to be selected. The “:1” in the WHERE
clause refers to the first column of the array that you pass to AP 127.
SQLDELETES
DELETE FROM STOCKS
WHERE ITEM = :1
You certainly want to store INVO somewhere, perhaps as a dummy last entry in
the ORDERS file.
Perhaps you should use an SQL DESCRIBE statement to get the column
descriptions and use these to drive your prompting, rather than having to reference
prompting matrices in your workspace. See APL2 Programming: Using Structured
Query Language (SQL) for information about using DESCRIBE.
Perhaps rows can be updated directly rather than through separate GET and PUT
statements.
Of course, you want to do whatever you do without loss of generality. Above all,
you want your code to retain its flexibility and readability. As you go through a
production test you will look for opportunities to reduce your logic to the essential,
while retaining the ability to respond to your user's needs. You know you can do
that, with APL2.
C E
each 9, 40, 47
carriage return character 64
language note 67
catenation with each 68
EACH 43, 47, 50
character set, APL2 5
F J
FILL 81 Jed's Wholesale Parts 24
test of 82
filling an order 26
find 34 L
first 38 label 4
FOO 41 lamp
FORMAT 74 See up shoe jot
format by example 75 language notes
format by specification 78 ambi-valence 50
formatting the report 73 appending arrays to arrays 59
functions 8 building nested arrays 51
ambi-valent 45 compress derived function 34
derived 8 handling events 36
display 11 operator syntax 45
dyadic 8 pick, each, and chipmunk 67
monadic 8, 45 limiting case
primitive 8 testing for in INPUT 35
fundamentals of APL2 3 loop
preventing an endless 36
G
GET 55 M
GETSQL 94 MATHFNS workspace 7
GETW 55, 56 matrices 10
MENU 59
MESSAGE 93
H monadic functions 45
handling events monadic operators 45
language note 36 MONTHS 76
multiply 8
I
IBM DATABASE 2 (DB2) 89 N
improving the application 94 NAMES 12
index 18
Index 97
negation 8 prompting for input 32
negative numbers 8 controlling sequences 39
negative take 77 doing without the prompt message 33
nested arrays 11 keeping the response on the same line 32
building 51 prompts
NEW 87 keeping them short 35
NEWCUST 63, 64 repeating 41
numbers PUT 53
complex 7 PUTSQL 53, 92
conventional form 7 PUTW 53
negative 8 test of 55
scaled form 7
numeric input
prompting for 35 Q
NUMIN 35 quad quote 32
O R
operator syntax rank 10, 11
language note 45 ravel 14
operators 8 ravel with axis 15, 41
defined 9 reduce 8, 9
defining your own 42 relational data 15
monadic 45 REPEAT 43, 44, 48
primitive 9 report
order data 28 formatting 73
order form 24 reshape 14
ORDERQ 39 RESTOCK 85
orders RESTOCKIN 86
filling 26 restocking merchandise 26
placing 25 rho ρ 10
ORDERS 30, 72
outer product 9
overbar S
used to represent negative numbers 8 selective specification 54
overstrike characters 6 shape 10
overtake 77 simple scalar 10
SQL (Structured Query Language) 52
SQL CREATE statement 52, 90
P SQL DELETE statement 94
path 20 SQL DESCRIBE statement 94
pick 20 SQL tables 89
language note 67 creating 91
pick-each-enclose 67 deleting data from 94
PLACE 63, 64, 66, 70 getting data from 94
test of 70 SQL/Data System (SQL/DS) 89
placing an order 25 SQLDELETES 94
positive take 77 SQLGROUP 92
PREP 92 SQLINSERTS 92
primitive functions 8 SQLSELECTS 93
primitive operators 9 SQLUPDATES 91
print SQLUPIN 93
formatting the invoice 69 statement 4
process STOCK 81, 82
actual orders 65 test of 88
updating the table 69
T
tables
designing 28
updating 52
take 46
terminal control 33
terminals
characteristics of those used with APL2 3
TOPL 77
TOPR 75
trying again 36
U
up shoe jot 31
UPDATE 56, 57
changing an address with 57
updating tables 52
user-friendly routines
writing 31
V
valid numbers
checking for 35
vectors 10
W
WARNING 49
without 33
WS FULL 36
Index 99
ACTORD MENU
[0] ACT←ACTORD ORD;STO;NEW [0] MENU
[1] ACTUAL ORDERS BASED ON STOCK AVAILABLE [1] SELECT AN ITEM
[2] ORD IS AN ITEM, QUANTITY [2] L1:
[3] ACT←ORD[1],0 [3] WHAT←INPUT CHOOSE,3OR,1ITEMS
[4] →(0=ρSTO←STOCKS GET↑ORD)/0 STOCK RECORDS [4] →(0=ρWHAT)/0 TERMINATE IF EMPTY RESPONSE
[5] NEW←[/0,STO[;4]|ORD[2] MAX NEW IS 0 [5] →(REQUEST NOT RECOGNIZED ERR*(WHAT)εITEMS)/L1
[6] IF NEW IS ZERO, THE ORDER IS ALL AVAILABLE [6] {←{EM[1;] {EA WHAT
[7] ACT[2]←(1 0=NEW=0)/STO[;4],ORD[2] [7] →L1 ASK AGAIN
[8] STO[;4]←NEW
NEW
[9] STOCKS PUT[2]STO UPDATE STOCK
[0] NEW;STO
[10] →(ACT[2]=ORD[2])/0
[1] ADD ITEMS TO STOCKS TABLE
[11] {←ORDER FOR STOCK STO[;1]REDUCED TO ACT[2]
[2] {←NEW STOCK ITEMS:
ADDRESS [3] STO←ASK EACH REPEAT STOCKQ
[0] Z←ADDRESS N;B [4] →(0=ρSTO)/0
[1] RETURNS THE ADDRESS(ES) OF CUSTOMER(S) N [5] STOCKS PUT STO
[2] IF NO ITEMS ARE FOUND, THE RESULT IS EMPTY
NEWCUST
[3] B←ε(1↑[2]CUSTOMERS)εN FIND OCCURRENCES OF ORDERS
[0] NEWCUST CUST;DATA
[4] Z←B1↓[2]CUSTOMERS SELECT ADDRESS VECTORS
[1] ADD A NEW CUSTOMER
CHECK [2] →(CUSTOMERS[;1]εCUST)/0 NOT NEW
[0] Z←CHECK;N [3] {←{TC[2],NEW CUSTOMER
[1] RETURN FIRST STOCK REPORT FOR SELECTED ITEM [4] →(0=ρDATA←ASK EACH 1↓[1]CUSTQ)/0
[2] N←NUMIN STOCK ITEMS TO CHECK [5] →(WARNING ERR (ρDATA)≠1+↑ρCUSTQ)/0
[3] →(0=ρZ←N)/0 [6] CUSTOMERS PUT,CUST,DATA UPDATE TABLE
[4] Z←STOCKS GET N [7] {←CUSTOMER RECORD UPDATED,{TC[2]
[5] →(0=ρZ)/0
PLACE
[6] Z←(,2¨STOCKQ),[1]Z
[0] PLACE;ORD;CUST;ORDQ
DATE [1] PLACE AN ORDER
[0] Z←DATE;{IO MONTH DAY, YEAR [2] →(0=ρCUST←ASK↑ORDERQ)/0 GET CUSTOMER NUMBER
[1] {IO←1 [3] NEWCUST CUST ADD NEW CUSTOMER
[2] Z←{TS [4] ORDQ←1↓[1]ORDERQ ITEMS TO PROMPT FOR
[3] Z←(MONTHS[Z[2];]* ), 50, 0000Z[3 1] [5] →(0=ρORD←ASK EACH REPEAT ORDQ)/0
[6] ACTORD FINDS ACTUAL ORDERS AND UPDATES STOCKS
FILL
[7] ORD←ACTORD EACH ORD
[0] FILL;N
[8] →(0=ρORD←(ε0≠2¨ORD)/ORD)/0 DELETE EMPTY ORDERS
[1] DELETES AN ORDER WHEN IT IS FILLED
[9] INVO←INVO+1 NEW INVOICE NUMBER
[2] N←NUMIN INVOICE NUMBER
[10] ORD←INVO,¨CUST,¨ORD APPEND TO ENTRIES
[3] →(0=ρN)/0 EXIT IF NO INPUT
[11] ORDERS PUT ORD UPDATE ORDERS
[4] ORDERS DELETE↑N
[12] FORMAT INVOICE↑↑ORD DISPLAY INVOICE
FORMAT
RESTOCK
[0] FORM←FORMAT INV;TOPR;TOPL;BODY;TOTAL
[0] RESTOCK;TYPES;MSG;LOW;A
[1] FORMATS A SINGLE INVOICE
[1] UPDATES THE STOCK TABLE
[2] SAMPLE CALL | FORMAT INVOICE 131
[2] LOW←(≯/STOCKS[;4 5])/STOCKS[;1]
[3] FORM←
[3] {←LOW ITEMS: LOW
[4] →(0=ρINV)/0
[4] MSG←,(0,STOCK NUMBER)(0,INCREMENT)
[5] TOPR←6↑[1]18↑¨(↑INV) DATE
[5] A←RESTOCKIN REPEAT MSG
[6] TOPL←6 30↑,ADDRESS(2INV)
[6] →(0=ρA)/0
[7] BODY←4 0 23 0 6 2 6 0 9 23INV
[7] STOCKSPUT,¨↑¨A UPDATE STOCK TABLE
[8] TOTAL←48↑8 2+/(3INV)[;5]
[9] BODY←BODY,[1] TOTAL RESTOCKIN
[10] FORM← ,[1](TOPL,TOPR),[1]BODY [0] Z←RESTOCKIN MSG;X
[1] CALLED ITERATIVELY BY RESTOCK
[2] L1:Z←ASK 1 1MSG GET STOCK NUMBER
[3] →(0=ρZ)/0
[4] X←STOCKS GET↑Z GET ROW OF STOCK TABLE
[5] →(0=ρX)/L1
[6] NOW READS: X
[7] Z←ASK 1 2MSG GET INCREMENT
[8] →(0=ρZ)/0
[9] X[;4]←X[;4]+↑Z ADD TO INVENTORY
[10] Z←,X
INVOICE STOCK
[0] INV←INVOICE N;STO;ORD [0] STOCK
[1] CALLED BY PLACE TO GET INVOICE DATA| [1] UPDATE THE STOCKS TABLE
[2] (INVOICE)(CUSTOMER)(ITEM,DESCRIPTION,PRICE,QUANTITY,COST) [2] CHECK ITEMS TO CHECK
[3] INV← [3] RESTOCK LOW ITEMS
[4] →(0=ρORD←ORDERS GET↑N)/0 GET ORDERS [4] NEW NEW ITEMS
[5] →(0=ρSTO←STOCKS GET ORD[;3])/0 GET STOCK RECORDS
[6] INV←(3↑[2]STO),1↑[2]ORD STOCK,DESCRIPTION,PRICE,QUANTITY
[7] INV←INV,/2↑[2]INV COST = PRICE QUANTITY
[8] INV←(2↑,ORD),INV INVOICE, CUSTOMER NUMBERS
Index 101
CUSTOMERS
DISPLAY CUSTOMERS
→|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
↓ →|||||||||||||||| →|||||||||||||| →|||||||||||||||||
7 CITY TRADERS INC 41 POSTAGE ROAD RIMELA, NY 12345
||||||||||||||||| ||||||||||||||| ||||||||||||||||||
→|||||||||||||| →||||||||||||||||| →|||||||||||||||||||||||||
55 MAIL HOUSE LTD 711 RAMBLERS LANE ISLAND CITY, S DAK 54321
||||||||||||||| |||||||||||||||||| ||||||||||||||||||||||||||
→||||||||||||||||| →||||||||||||||||||| →|||||||||||||||||||
312 MANTUP SALES CORP RURALIA FARMS, RFD 2 SUBURBIA, WIS 00000
|||||||||||||||||| |||||||||||||||||||| ||||||||||||||||||||
ε|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CUSTQ ORD STOCKQ
DISPLAY CUSTQ DISPLAY ORD DISPLAY STOCKQ
→|||||||||||||||||||||||||| →||||||||||||| →|||||||||||||||||||||||
↓ →|||||||||||||||||||| ↓131 55 3569 10 ↓ →|||||||||||||||||
→|||||||||||||| *||||||||||||| →|||||||||||
0 CUSTOMER NUMBER 0 STOCK NUMBER
ORDERQ
||||||||||||||| ||||||||||||
DISPLAY ORDERQ
ε|||||||||||||||||||| ε|||||||||||||||||
→||||||||||||||||||||||||
→||||||||||||||||||| →|||||||||||||||||
↓ →||||||||||||||||||||
→||||||||||||| →|||||||||||
→||||||||||||||
CUSTOMER NAME DESCRIPTION
0 CUSTOMER NUMBER
| |||||||||||||| | ||||||||||||
|||||||||||||||
ε||||||||||||||||||| ε|||||||||||||||||
ε||||||||||||||||||||
→||||||||||||| →|||||||||||
→|||||||||||||||||
→||||||| →|||||
→|||||||||||
ADDRESS 0 PRICE
0 STOCK NUMBER
| |||||||| ||||||
||||||||||||
ε||||||||||||| ε|||||||||||
ε|||||||||||||||||
→|||||||||||||||||||||| →|||||||||||||||
→|||||||||||||||||
→|||||||||||||||| →|||||||||
→|||||||||||
CITY, STATE, ZIP 0 INVENTORY
0 AMT ORDERED
| ||||||||||||||||| ||||||||||
||||||||||||
ε|||||||||||||||||||||| ε|||||||||||||||
ε|||||||||||||||||
ε|||||||||||||||||||||||||| →|||||||||||||||||||
ε||||||||||||||||||||||||
→|||||||||||||
INVO
ORDERS 0 REORDER LEVEL
DISPLAY INVO
DISPLAY ORDERS ||||||||||||||
140
→|||||||||||||| ε|||||||||||||||||||
ITEMS ↓123 55 3569 5 ε|||||||||||||||||||||||
DISPLAY ITEMS 123 55 1135 12
STOCKS
→||||||||||||||||||||||| 123 55 2583 4
DISPLAY STOCKS
→|||| →||| →|||| 131 55 3569 10
→|||||||||||||||||||||||||||||||||||||||||
PLACE FILL STOCK 135 312 9998 12
↓ →|||||||||||||||
||||| |||| ||||| 136 7 1135 2
1135 FIRST GREAT ITEM 995 118 55
ε||||||||||||||||||||||| *||||||||||||||
||||||||||||||||
MONTHS PROMPTS →||||||||||||||||
DISPLAY MONTHS DISPLAY PROMPTS 9993 HIGH FLYER WIDGET 8873 240 35
→|||||||| →|||||||||||||||||||||||||| |||||||||||||||||
↓JANUARY →||||| →|||| →||||| →||||||||||||||||
FEBRUARY STOCKQ CUSTQ ORDERQ 3569 SECOND MONEYMAKER 2475 10 30
MARCH |||||| ||||| |||||| |||||||||||||||||
APRIL ε|||||||||||||||||||||||||| →|||||||||||||||||
MAY 5613 MAIL ORDER SPECIAL 1499 225 95
JUNE ||||||||||||||||||
JULY →||||||||||||
AUGUST 2583 A REAL WINNER 4999 89 10
SEPTEMBER |||||||||||||
OCTOBER →|||||||||||||||
NOVEMBER 9998 NONESUCH FRAMMIS 269 416 50
DECEMBER ||||||||||||||||
||||||||| →||||||
7777 THINGY 189 2 1
|||||||
→||||
8888 WHEEE 433 3011 100
|||||
→|||||||
5555 WHIZBANG 2222 43 2
||||||||
ε|||||||||||||||||||||||||||||||||||||||||
WARNING
DISPLAY WARNING
→||||||||||||||||
***DATA NOT SAVED
|||||||||||||||||
History Sheet
Date: 03/02/92
Title: APL2 Programming: Guide
Order No./TNL#/Activity: SH20-9217-00
File Prefix: G20P1
EA/Writer/Editor: Dana Marsh/Janet Walters/Laura Nystrom
Development Book Owner David Liebtag
Graphics Consultant Kathy Holland
ISIL Version: BookMaster
Output Device: 3820 4250
Support: 1.5 1.25
Index 103
Common art? Yes No
Board Art? Yes No
IPG Art? Yes No
Common files? Yes No
Common File Name(s): None
Special Style File Name: IBMXAGD
Comments:
Changes to this book were minor - there were no formal information inspection reviews.
There are four special fold out pages at the back that are reference and do not get listed in the table of contents. A
special file was created as place holders for the printer. There was a miscommunication with the printer and the
pages are listed in the table of contents. This will need to be corrected on the next revision of this book.
Please use one of the following ways to send us your comments about this book:
Mail—Use the Readers' Comments form on the next page. If you are sending the form
from a country other than the United States, give it to your local IBM branch office or
IBM representative for mailing.
Fax—Use the Readers' Comments form on the next page and fax it to this U.S. number:
(408) 463-4488.
Electronic mail—Use one of the following network IDs:
– IBMMail: USIB6JN8
– Internet: [email protected]
Be sure to include the following with your comments:
– Title and publication number of this book
– Your name, address, and telephone number if you would like a reply
Your comments should pertain only to the information in this book and the way the informa-
tion is presented. To request additional publications, or to comment on other IBM informa-
tion or the function of IBM products, please give your comments to your IBM representative
or to your IBM authorized remarketer.
Very Very
Satisfied Satisfied Neutral Dissatisfied Dissatisfied
Technically accurate
Complete
Easy to find
Easy to understand
Well organized
Applicable to your tasks
Grammatically correct and consistent
Graphically well designed
Overall satisfaction
Name Address
Company or Organization
Phone No.
Cut or Fold
Readers' Comments
IBM
Along Line
SH21-1072-00
NO POSTAGE
NECESSARY
IF MAILED IN THE
UNITED STATES
Cut or Fold
SH21-1072-00 Along Line
IBM
SH21-172-