Programming Corner from Icon Newsletter 6
May 1, 1981; Icon Version 2
An Idiom
Every programming language has a number of particularly apt idioms. Consider
the expression
x <- x
At first sight, this expression appears to be a curiosity. However, when
used in a conjunction expression, it serves as a stack with automatic pushing
of the value of x
when it is evaluated and automatic popping
of the value of x
during backtracking. Thus, in
expr1 & (x <- x) & expr2
if expr1
succeeds, the value of x
is pushed and
expr2
is evaluated. If expr2
fails, the value
of x
is popped and expr1
is reactivated.
In situations in which several expressions are connected by conjunction
to obtain the first-in, last-out sequencing provided by goal-directed evaluation,
this reversible-assignment idiom is both concise and (once it is understood)
clearly indicates its purpose.
Solutions to Questions Posed in Newsletter 5
In the programming corner of Newsletter 5, several programming questions
were posed. These questions are restated below with their answers.
Problem 1: Q: What is the output produced by each of the following
expressions?
(a) every write((0 | 0) to 7)
(b) every write(0 to 3,0 to 7,0 to 7)
(c) every write(1 | 2 to 3 | 4 by 1 | 2)
(d) every 1 to 3 do every write(1 to 3)
A: These expressions illustrate the use of every to force generators through
all their results. The left-to-right, last-in first-out order of results
is shown by the output below. Ellipses are used to compress long sequences
where the output follows an obvious pattern.
(a) 0 (b) 000 (c) 1 (d) 1
1 001 2 2
2 . 3 3
3 . 1 1
4 . 3 2
5 076 1 3
6 077 2 1
7 100 3 2
0 101 4 3
1 . 1
2 . 3
3 . 2
4 176 3
5 177 2
6 200 2
7 201 3
. 4
. 2
. 4
276
277
300
301
.
.
.
376
377
Problem 2: Q: For arbitrary procedures f(x,y)
and g(x,y)
,
what is the sequence of calls produced by
every (f | g)(1 to 3, 4 | 5)
A:
f(1,4)
f(1,5)
f(2,4)
f(2,5)
f(3,4)
f(3,5)
g(1,4)
g(1,5)
g(2,4)
g(2,5)
g(3,4)
g(3,5)
Problem 3: Q: Given
s1 := "aeiou"
s2 := "abecaeioud"
what are the outcomes of
(a) (find | upto)(s1,s2)
(b) (find | upto)(s2,s1)
(c) (if size(s1) > size(s2) then upto else find)(s1,s2)
A: The answer to this question illustrates that functions are data objects
and that function application involves applying the value of a (function-valued)
expression, such as (find | upto)
. In addition, goal-directed
evaluation applies to such expressions themselves. In fact, an expression
such as expr(expr1,
...,
exprn)
involves
the mutual goal-directed evaluation of expr, expr1,
...,
exprn
in which the value of expr is applied to expr1,
...,
exprn
. The outcomes for the expressions above
are
(a) 5
(b) 1
(c) 5
Problem 4: Q: What are the outcomes of the following expressions?
(Note any that produce errors.)
(a) (x | y) := 3
(b) (x & y) := 3
(c) (1 & x) := 3
(d) (x & 1) := 3
(e) (x + 1) := 3
A: The term outcome is used in the technical sense here. As indicated, the
outcomes of the first three expressions are variables, since assignment
returns its left operand as a variable.
(a) x (assigned the value 3)
(b) y (assigned the value 3)
(c) x (assigned the value 3)
(d) error (variable expected)
(e) error (variable expected)
Problem 5: Q: Given the procedure
procedure drive(x)
fail
end
What is the output produced by
(a) drive(write(1 to 7))
(b) drive(write(0 to 7,0 to 7))
A: This problem illustrates the relationship between goal-directed evaluation
and the control structure every
, which forces generators to
produce all their results. The same effect can be produced by a procedure
that only fails, hence forcing goal-directed evaluation to produce all the
results of its argument.
(a) 1 (b) 00
2 01
3 02
4 .
5 .
6 .
7 06
07
10
11
12
.
.
.
66
67
70
71
72
.
.
.
76
77
Problem 6: Q: What does the execution of the following program do?
procedure main()
f(f := write)
end
A: This one is tricky. Since the program has no procedure declaration for
f
, one might suppose the execution of the program is an error.
Recall Problem 3 above, however, noting that function and procedure applications
are evaluated the same way. Furthermore variables are not dereferenced until
all the arguments are evaluated. This applies to the "zeroth"
argument, which is the function or procedure to be applied. Evaluation of
the first argument assigns a function value to f
(i.e., the
value of write
). Hence this expression is equivalent to write(write)
and produces the output
function write
(The form of the output is a consequence of "imaging" a non-string
value for the purposes of output. This is the same imaging that is used
in tracing procedure calls.)
Problem 7: Q: The following procedure is proposed as a generator
of "words" -- strings of consecutive letters -- in the lines of
the input file. It does not work properly, however. What does it actually
do and what is the cause of the problem? Rewrite the procedure to work properly.
procedure genword()
local line
static letters
initial letters := &lcase ++ &ucase
while line := read() do
line ?
while tab(upto(letters)) do
suspend tab(many(letters))
end
A: The problem lies in the suspend
expression. suspend
is like every
-- it forces its argument to generate all its
results. Although neither tab
nor many
have alternative
results, tab
does restore the value of &pos
if it is resumed to produce a second result. Hence, &pos
is always restored to its position prior to the first word and this procedure
loops, continually returning the first word of the first line of input!
There are two ways of circumventing this problem: use of an auxiliary identifier
or explicitly preventing generation of alternatives in the suspend expression,
and hence the backtracking done by tab
. Thus, the while
loop can be rewritten as
while tab(upto(letters)) do
{
t := tab(many(letters))
suspend t
}
or
while tab(upto(letters)) do
suspend tab(many(letters)) \ 1
Incidentally, this problem is sufficiently insidious that it deserves attention
in the design of Icon. The subtlety of the problem lies in the fact that,
except for reversible assignments, Icon does data backtracking only in tab
and move
.
Icon home page