The ABC Programmer's Handbook by Leo Geurts, Lambert Meertens, and Steven Pemberton
HOW TO PRINT CELSIUS FROM a TO b: PUT a, b IN lo, hi IF lo > hi: PUT hi, lo IN lo, hi \Swap hi and lo FOR f IN {lo..hi}: PUT (f-32)*5/9 IN c WRITE f, "Fahrenheit =", 2 round c, "Celsius" /
Once this piece of ABC (the definition of a command PRINT
CELSIUS
for listing temperatures in degrees Fahrenheit converted to
degrees Celsius) has been entered, you can give this command:
PRINT CELSIUS FROM 40 TO 45
You then get the following on your screen (we use a slanting font for output from ABC):
40 Fahrenheit = 4.44 Celsius 41 Fahrenheit = 5.00 Celsius 42 Fahrenheit = 5.56 Celsius 43 Fahrenheit = 6.11 Celsius 44 Fahrenheit = 6.67 Celsius 45 Fahrenheit = 7.22 Celsius
The text of this how-to is straightforward:
a
and b
, which are in lower case.
PUT
has an expression to the left of
IN
, and an address to the right. The expression is
evaluated to give a value, and the address to give a
location. The value is then put in the location. As we shall see
later, this multiple PUT
command in fact puts one
compound value in one compound location. The second
PUT
command swaps the values of lo
and
hi
. \
', and
continues to the end of the line. A comment can come after a command, or
occur on a line by itself.IF
or FOR
, controls
the indented commands following the corresponding colon. Thus, the user
need not bother about pairs of BEGIN
and END
or
other delimiters for grouping commands. For the same reason, the whole body
of the how-to has been indented. If there is only one controlled command,
the whole construction may be displayed on a single line:
IF a < 0: PUT -a IN a
FOR
command traverses the list of numbers which starts
at lo
and ends with hi
.round
function in the WRITE
command rounds
its right operand to 2 digits after the decimal point, as indicated by its
left operand./
at the end of the WRITE
command specifies
transition to a new line.In the ABC system a friendly editor takes care of the tedious aspects of
typing the program, such as indentation and other layout matters, capitals,
keywords such as PUT
and WRITE
, etc. This is
explained in chapter 3, Using ABC.
The power of ABC is largely due to its carefully designed system of data types and associated operations. There are two basic types - numbers and texts and three structures creating new types from existing ones - compounds lists and tables
Integers, that is, whole numbers, in ABC are unusual in that there is no
restriction on length. Furthermore, integers are just a special case of
exact i.e. rational, numbers (integral fractions). For example,
1.25
is an exact number. Calculations on exact numbers with the
operators +
, -
, *
(multiplication) and
/
(division), give exact results.
In addition to exact numbers, there are approximate (floating
point) numbers. The operator ~
is used for conversion to an
approximate number: ~22/7
does not return an exact, but an
approximate number. Calculations with the mathematical functions like
root
, sin
, and log
, return approximate
numbers. The function exactly
converts an approximate number to
exact.
Mixed arithmetic is allowed, as in 2*sin(x-1)
.
Operations: | Examples: |
---|---|
plain arithmetic | +x , x+y , -x , x-y ,
x*y , x/y |
to the power | 3**2 = 9 |
round upwards | ceiling 3.68 = 4 |
round downwards | floor 3.68 = 3 |
round to nearest integer | round 3.68 = 4 |
round to n digits after decimal point |
1 round 3.68 = 3.7 |
sign | sign 3.68 = 1 |
absolute value | abs(-3.68) = 3.68 |
square root | root 2 = ~1.4142135623731 |
n -th root |
3 root 2 = ~1.2599210498949 |
remainder after division | 27 mod 10 = 7 |
numerator of exact number | */ 1.25 = 5 |
denominator of exact number | /* 1.25 = 4 |
natural logarithm | log 10 = ~2.302585092994 |
logarithm to base b |
2 log 8 = ~3 |
exponential function | exp 1 = ~2.718281828459 |
trigonometric functions in radians | sin x, cos x, tan x,
arctan x, angle(x,y) |
trigonometric functions in degrees | 360 sin x, 360 cos x, 360 tan x, 360 arctan x, 360 angle (x,
y) |
distance from origin | radius(x, y) |
The functions */
and /*
yield the numerator and
the denominator of an exact number. For this purpose, fractions are
automatically reduced to lowest terms; so 1.25 = 125/100
is
reduced to 5/4
. Again, there is no restriction on the lengths of
the numerator and denominator. The following definition for the
greatest-common-divisor function of two exact positive numbers uses this
property (as long as a
and b
are not 0):
HOW TO RETURN gcd(a, b): RETURN a / */(a/b)
"Merry Christmas"
is an example of a text. Like numbers, there
is no restriction on the length of texts.
Operations: | Examples: |
---|---|
join | "now"^"here" = "nowhere" |
repeat | "-"^^5 = "-----" |
tail | "nowhere"@3 = "where" |
head | "nowhere"|2 = "no" |
number of characters | #"nowhere" = 7 |
number of occurrences | "e"#"nowhere" = 2 |
selection | "nowhere" item 4 = "h" |
traverse the characters | FOR c IN "nowhere": |
smallest character | min "nowhere" = "e" |
largest character | max "nowhere" = "w" |
convert to upper case | upper "NowHere" = "NOWHERE" |
convert to lower case | lower "NowHere" = "nowhere" |
strip spaces | stripped " nowhere " = "nowhere" |
For the operations @
, |
, and item
,
the elements of a text are numbered from 1 to the length of the text.
The operations @
and |
may be combined to yield
any subtext; for example, to obtain the subtext starting at position 3 and
having length 4, we may write:
"nowhere"@3|4 = "wher"
Such text selections may also be used as addresses. For instance, after
PUT "nowhere" IN word PUT "bless" IN word@3|4
the location word
contains "noblesse"
(since
wher
has been replaced by bless
).
A compound is a sequence of values of possibly different types. It is like a
record in other languages, but it has no field names. Compounds are useful for
multiple PUT
's, for parameters, for multi-dimensional tables
(t[i, j]
), but also as values to be put in a table (instead of
separate values put in separate tables under the same key). Examples:
PUT a, b, c IN b, c, a \cyclic permutation PUT "May", 22 IN anniversary PUT 1813, anniversary IN date PUT date IN year, (month, day) PUT now IN (year, month, day, hour, minute, second)
Operation: | Example: | Gives: |
---|---|---|
Date and time | WRITE now |
(1995, 7, 26, 13, 13, 16.976513) |
The function now
returns the date and time now, with the
seconds accurate to that supplied by the operating system.
A list is a sorted sequence of values with multiple occurrences allowed. For example,
WRITE {"u"; "e"; "a"; "y"; "o"; "i"}
gives {"a"; "e"; "i"; "o"; "u"; "y"},
WRITE {3; 2; 1}
gives {1; 2; 3}, and
FOR i IN {"ay"; "i"; "eye"; "aye"}: WRITE #i
gives 2 3 3 1 (which is because {"ay"; "i";
"eye"; "aye"}
gives {"ay"; "aye"; "eye"; "i"}
).
The items of a list may be of any type, but all must be of the same type
(all texts, or all numbers, etc.) Since there may be multiple occurrences of an
entry, you may have lists like {1; 1; 1}
.
Sorted lists of any type work because an order is associated with each type. For instance:
2.99 < 3.00 "" < "Z" < "a" < "az" < "b" ("b", "c") < ("z", "a") {} < {"a"; "z"} < {"b"}
Instead of {1; 2; 3; 4; 5; 6; 7}
the range notation
{1..7}
may be used; similarly, {"a".."c"}
stands for
{"a"; "b"; "c"}
. Several of these ranges may be combined: the list
{"A".."C"; "a".."c"}
contains six items.
Operations: | Examples: |
---|---|
initialise | PUT {"wart"; "eye"; "mouth"} IN face |
add item to list | INSERT "nose" IN face |
remove one instance | REMOVE "wart" FROM face |
number of items | #face = 4 |
number of occurrences | "eye"#face = 2 |
traverse the items | FOR f IN face: INSERT f IN f2 |
smallest (= first) item | min {3; 2} = 2 |
largest (= last) item | max {3; 2} = 3 |
selection | {1; 9; 8; 9} item 2 = 8 |
Because lists are kept sorted there is no need for sorting programs. If a different order is required for the items of a list, it is often useful to create a list of compounds. Suppose texts are wanted in length order:
PUT {} IN new.face FOR feature IN face: INSERT #feature, feature IN new.face
To output the texts in the desired order, you can then use:
FOR length, feature IN new.face: WRITE feature /
(note the use of a multiple name following FOR
) which would
produce:
eye eye nose mouth
Tables are like arrays or tables in other languages, but the keys (= indices) may be of any type, and need not be consecutive:
PUT {} IN price PUT 3.50 IN price["jam"] PUT 1.95 IN price["bread"] PUT 2.45 IN price["butter"] WRITE price
gives as output on the screen:
{["bread"]: 1.95; ["butter"]: 2.45; ["jam"]: 3.50}
Notice that the entries are sorted on the keys. All keys of a table must have the same type, but just as with lists, may be of any type. The items (the stored values) must also all have the same type, but, of course, the types of the keys and the items need not be the same.
Operations: | Examples: |
---|---|
initialise | PUT {} IN count |
add entries | FOR f IN face: PUT 0 IN count[f] |
modify entries | FOR f IN face: |
the list of keys | keys count = {"eye"; "mouth"; "nose"} |
length | #count = 3 |
number of occurrences in the items | 2#count = 1 |
number of occurrences in the keys | "nose" # keys count = 1 |
traverse the items | FOR i IN count: PUT s+i IN s |
traverse the keys | FOR f IN keys count: WRITE f, count[f] / |
smallest item | min count = 1 |
smallest key | min keys count = "eye" |
largest item | max count = 2 |
largest key | max keys count = "nose" |
selection of an item | count item 2 = 1 |
selection of a key | (keys count) item 2 = "mouth" |
delete entry | DELETE count["nose"] |
The keys
function plays an important role in nearly all
applications of tables.
There is one function on texts, split
, that splits a text into
its space-separated words and returns these as a table:
split " now here " = {[1]: "now"; [2]: "here"}
As you may have noticed, several of the operations mentioned work equally for texts, lists and tables. Texts, lists and tables together are called trains. The generic operations are:
Operations: | Meaning: |
---|---|
# |
the length |
min |
the smallest item |
max |
the largest item |
item |
selection of an item |
The FOR
command also works generically on trains.
When a generic operation is applied to a table, it is the items of the table
that are handled, and not the keys, so min t
, where t
is a table, gives the smallest item, and min keys t
gives
the smallest key.
For min
and max
there are also versions with two
operands: i min t
gives the smallest item of t
that
is larger than i
. Similarly, i max t
gives the
largest item of t
smaller than i
. For instance,
"i" min {"a"; "e"; "i"; "o"; "u"; "y"} = "o" "i" max {"a"; "e"; "i"; "o"; "u"; "y"} = "e"
As shown in the PRINT CELSIUS
example, new commands may be
defined with how-to's:
HOW TO APPEND tail TO text: PUT text^tail IN text
HOW TO PREPEND head TO text: PUT head^text IN text
HOW TO PRINT text VERTICALLY: FOR c IN text: WRITE c /
HOW TO REVERSE text: PUT "" IN reverse FOR c IN text: PREPEND c TO reverse PUT reverse IN text
Once these have been given, the commands
PUT "here" IN word PREPEND "now" TO word REVERSE word
make word
equal to "erehwon"
. If we now say
REVERSE word@4|2
word
is made equal to "erewhon"
.
So a command how-to is to ABC what a procedure or sub-routine is to other
languages. The general appearance of user-defined commands is the same as that
of predefined commands such as PUT
... IN
... (though
no control commands of the type WHILE
... : can be defined by the
user).
Similarly, users may define their own functions, such as this one which returns the set of different items occurring in a given list:
HOW TO RETURN elements collection: PUT {} IN unique FOR element IN collection: IF element not.in unique: INSERT element IN unique RETURN unique
The name of the function (elements
) and its template operand
(collection
) occur in the first line (values passed to functions
are called operands and those passed to commands are called
parameters. The how-to should contain one or more RETURN
commands, specifying the result, which may be of any type, just as the operand
may be.
As well as functions with one operand, you may also define functions with
two operands, and functions without operands. Here is a function called
with
that has two operands list1
and
list2
, and a function origin
that has no operands:
HOW TO RETURN list1 with list2: FOR i IN list1: INSERT i IN list2 RETURN list2
HOW TO RETURN origin: RETURN (0, 0, 0)
Functions with more than two operands have to be written as functions with one or two compound operands, e.g.,
HOW TO RETURN (x, y, z) rotated (pitch, roll, yaw):
The function with
above appears to change the value of its
operand, but such a change does not affect any location outside the how-to: it
changes a copy of the operand which is thrown away upon termination of
the invocation of the function.
In contrast to functions, the execution of a command defined by a
HOW TO
does change the environment when something is
PUT
IN
a parameter; in fact, that is the very purpose
of commands. When a operand is given to a function, only its value is passed,
whereas in the case of a command, the value of the parameter is passed if it
has one, and then after the execution of the command, if the value has been
changed it is passed back.
The operands for functions and the parameters for commands require no
parentheses (except to indicate priorities). For instance, you can write
sin x
and 2*sin x
.
However, you cannot write sin x * 2
since it is ambiguous, and
the system will tell you so. You must write either (sin x) * 2
or
sin(x*2)
, depending on which you mean. Values of any type may be
passed to a how-to, as long as they match up with the operations used in the
how-to. For instance, the elements
function above will work on any
train as operand:
WRITE elements "Mississippi"
gives as output
{"M"; "i"; "p"; "s"}
If we use a command such as
PUT {2; 3; 5; 7} IN primes
as an immediate command (as opposed to inside a how-to), a location is
created - unless one already exists - whose name is primes
. The
location stays in existence until the command DELETE primes
is
given, and so is referred to as a permanent location. Names of
locations used inside a how-to are private to that how-to, and the
corresponding locations are temporary: they are alive only as long as the
invocation of the how-to lasts.
One way of importing permanent locations into a how-to is by using
parameters, but there is also another way. If, in the example of the previous
section, there is only a single text for APPEND
,
REVERSE
, etc., to work on, it is a nuisance to have to pass it as
a parameter each time. The how-to's may then be changed to:
HOW TO APPEND tail: SHARE text PUT text^tail IN text
HOW TO PRINT VERTICALLY: SHARE text FOR c IN text: WRITE c /
etc.
SHARE text
indicates that the name text
in this
how-to will not use a location private to the how-to, but will refer to a
shared permanent location, either already in existence, or to be created during
the invocation of the how-to. Other how-to's wishing to refer to the same
text
should now also SHARE text
, since otherwise
their use of the name text
would be private and would introduce a
temporary location.
A location should be used consistently for values of one type. Private names
may be used for different types of values during the execution of different
invocations, but during one invocation the corresponding location may contain
only values of the same type. So, if we have defined this SWAP
command:
HOW TO SWAP a AND b: PUT a, b IN b, a
we may use it at one time to swap two numbers, at another time to swap two texts, but never to swap a number and a text.
The system checks this partly statically, i.e. at the time the how-to's are typed in, partly dynamically, i.e. during execution time. Checking is done on a basis of consistency, not on a basis of conformity to declarations, which do not exist in ABC.
There are two conditional commands. In addition to
IF test:
(no ELSE
allowed), ABC also has
SELECT: test1: ... test2: ... test3: ...
The first test to succeed determines which alternative is to be executed. At
least one test must succeed. Instead of the last test, the keyword
ELSE
may be used, which always succeeds:
SELECT: e > 0: INSERT e IN pos e < 0: INSERT e IN neg ELSE: PUT nzero+1 IN nzero
For repetition, apart from FOR
... IN
..., there
is also
WHILE test: ...
to repeatedly execute a group of commands while the test is true.
For leaving a command how-to before its end, the command
QUIT
is used. It may occur anywhere inside a command how-to.
The function random
returns an arbitrary approximate number,
such that
0 <= random < 1 .
The function choice
returns an arbitrary item from a train. For
instance, the command
PUT choice {"A".."Z"} IN cap
puts an arbitrary capital letter in cap
. Likewise,
PUT choice "Mississippi" IN c
puts an arbitrary letter from the text "Mississippi"
in
c
. In the same way, it is possible to make an arbitrary
choice
from the items of a table. The meaning of
choice
may be described in terms of random
in the
following way:
HOW TO RETURN choice train: RETURN train item (1+floor(random*#train))
(This is another example of a how-to which may be used with operands of different types - text, list or table in this case - as long as they are consistent with the operations inside.)
The behaviour of choice
and random
is based on a
pseudo-random sequence. To make programs replicable, the sequence may be
started at a specified point by using a SET RANDOM
command, e.g.
like this:
SET RANDOM "Today is my birthday."
or with an invocation with any other expression of any type as a parameter.
An order is associated with each type, so <
,
<=
, =
, >=
, >
and
<>
(unequal) are defined for all values having the same type
(but not for values of different types). Multiple comparisons are allowed:
WHILE x < y < z: PUT f y IN y
To discover if a list or table (or text) contains a certain item (or
character), the in
test may be used:
SELECT: i in keys count: PUT count[i]+1 IN count[i] ELSE: PUT 1 IN count[i]
Similarly, the test not.in
tests the absence of an item.
To discover if a number is exact or approximate, the test exact
is used:
SELECT: exact x: WRITE */ x, "over", /* x ELSE: WRITE "~", x
Tests may be combined with AND
, OR
and
NOT
:
IF i in keys t AND t[i] > 2: ... IF NOT (c = "a" OR c = "b"):.
Such combined tests are performed from left to right. In an AND
combination, evaluation stops as soon as a failing test is encountered; in an
OR
combination it stops at the first succeeding test.
ABC also has versions of the mathematical quantifiers SOME
,
EACH
and NO
:
IF SOME d IN {2..n-1} HAS n mod d = 0: WRITE n, "has factor", d
If the SOME
test succeeds, the first value found to satisfy the
test following HAS
is put in the location of the name between
SOME
and IN
. Examples of NO
and
EACH
are:
IF NO year, anniversary IN birthday HAS year = 1813: WRITE "list is incomplete"
SELECT: EACH name IN keys birthday HAS name|1 in "ABC": WRITE "ABC-composers abound" ELSE: WRITE name
Just as a new function may be defined with HOW TO RETURN
, a new
predicate to be used in tests may be defined with HOW TO REPORT
:
HOW TO REPORT a includes b: REPORT EACH x IN b HAS x in a
In a predicate how-to, REPORT
is used instead of
RETURN
to indicate the outcome. To avoid REPORT 1 = 1
or REPORT 0 = 1
, the commands SUCCEED
and
FAIL
are available as well. Another version of the
includes
how-to might be:
HOW TO REPORT a includes b: FOR x IN b: IF x not.in a: FAIL SUCCEED
As with HOW TO RETURN
, operands are passed by value and the
environment is not affected by changes made by a predicate how-to.
Input from the user at the keyboard is read by a READ
command,
such as
READ size EG (0, 0)
where the expression following EG
specifies the type of the
expression to be entered (here a compound of two numbers). The type of the
expected expression may be given by, for example, 0
or
""
, or by any other expression. In the following example, the
READ
command demands the input to be of the same type as the items
of the list legal.moves
:
READ move EG min legal.moves
READ
prompts the user, who may then enter any expression of the
desired type, using shared names and built-in or user-defined functions.
READ
with an EG format requires input texts to be quoted, just
as in ABC itself. For situations where this is a nuisance, another version of
the READ
command is available:
READ line RAW
which interprets the input line as a text and stores it in the location
line
.
For output to the screen, a WRITE
command is used. As shown
above, WRITE
can handle expressions of any type. Apart from the
operations mentioned in See
Texts, the following operations are useful for formatting purposes:
Operation: | Examples: |
---|---|
align left | 7*8<<9 = "56 " |
align right | 7*8>>9 = " 56" |
centre | 7*8><9 = " 56 " |
insert value of expression | "1K is `2**10`" = "1K is 1024" |
These operations work for values of any type, and aren't confined to
WRITE
commands; for instance:
PUT "1K is `2**10`" IN message
Hierarchical programming in ABC is aided by refinements. These are like light-weight procedures, but:
Refinements come in three kinds, analogous to the three kinds of how-to's. All three kinds are seen in this how-to to compute the average of a sequence of positive numbers. Note that just as with commands, functions and predicates, the name of a command-refinement is in capital letters, and the names of function- and predicate-refinements are in lower-case:
HOW TO AVERAGE: INITIALISE GET INPUT WHILE valid: TREAT GET INPUT OUTPUT INITIALISE: PUT 0, 0 IN sum, n GET INPUT: WRITE "Number: " READ i EG 0 TREAT: PUT sum+i, n+1 IN sum, n valid: REPORT i > 0 OUTPUT: WRITE "The average is:", average average: RETURN sum/n
Note that function refinements contain a RETURN
command, and
predicate refinements a REPORT
command. Similarly, command
refinements may be left early with the QUIT
command. For instance,
here is a way of writing the elements of a train until an element is greater
than 100:
LOOP: FOR x IN train: IF x > 100: QUIT WRITE x
Of course, refinements become more useful as programs become longer, and you will see many in the coming chapters.
The ABC Programmer's Handbook by Leo Geurts, Lambert Meertens, and Steven Pemberton
Copyright © 1990, 2002, Leo Geurts, Lambert Meertens, and Steven Pemberton. All rights reserved.