Programming Corner from Icon Newsletter 3
February 12, 1980; Icon Version 2
Experience with Icon has shown that the full use of its facilities requires
different ways of thinking about programming than are used in other languages.
In the last Newsletter we mentioned some of the problems encountered in
attempting to use SNOBOL-style programming techniques in Icon.
The built-in scanning functions tab(i)
and move(i)
can be easily coded as Icon procedures. This exercise is helpful both in
understanding tab(i)
and move(i)
better and in
illustrating some Icon idioms. Since tab(i)
and move(i)
differ only in that the former changes &pos
absolutely
while the latter changes &pos
relatively, only tab(i)
will be considered here. A naive procedure for tab(i)
is
procedure tab(i)
return &subject[&pos,&pos := i]
end
Before discussing the defects in this solution, aspects of the returned
expression should be noted. In the first place, the value of &pos
:= i
is the value of i
. Thus the substring of &subject
returned is between the old and new values of &pos
. Furthermore,
if &pos := i
fails (because i
is out of range
of &subject)
, &pos
is not changed and
the failure of &pos := i
is inherited by subscripting and
in turn by return
. Hence tab(i)
fails as it should.
One defect in this procedure is that it does not restore &pos
to its former value in case tab(i)
succeeds, but the enclosing
expression in which it occurs fails. Consider, for example
tab(i)
& find(s)
Suppose tab(i)
succeeds (thus changing &pos
)
but find(s)
fails. In this case, the built-in version of tab(i)
restores &pos
to its previous value. This is one of the
few cases in Icon where there is data backtracking (the others are move(i)
,
=s
, x <- y
, and x <-> y
).
From the point of view of expression evaluation, the failure of find(s)
in
tab(i) & find(s)
results in a request for an alternative value for tab(i)
. While
tab(i)
itself is not a generator (it can only set &pos
to the value of i
), the request for an alternative value allows
tab(i)
to gain control in order to restore &pos
to its former value. This can be accomplished in the procedure above by
a few minor modifications:
procedure tab(i)
local j
j := &pos
suspend &subject[&pos,&pos := i]
&pos := j
fail
end
The local identifier j
provides storage for the original value
of &pos
. suspend
is used in place of return
so that the procedure can regain control if an alternative value is requested.
When control is regained, &pos
is restored to its former
value and tab(i)
returns with a failure signal.
As a detail, it is worth noting that if subscripting fails because &pos
:= i
fails, the suspend
does not return to the calling
procedure and control falls through to the (unnecessary but correct) expression
that follows. That is, while return
inherits its signal from
the success or failure of its argument, suspend
(in the style
of every
) returns the alternatives of its argument -- if its
argument fails at once, there are no alternatives to return.
The argument of suspend
in the procedure above can be made
somewhat more compact at the expense of clarity as follows:
suspend &subject[j := &pos,&pos := i]
in which case the immediately preceding expression can be omitted. In addition,
the restoration of &pos
to its former value can be accomplished
by using reversible assignment, thus eliminating the need for j
and the explicit save and restore of &pos
. Thus the procedure
becomes:
procedure tab(i)
suspend &subject[&pos.&pos <- i]
fail
end
The fail
expression is still needed, since suspend
,
as noted above, cannot fail.
In order to have confidence that the value of &pos
is restored
in the procedure above, it is necessary to appreciate that when the procedure
is reactivated for an alternative value, suspend
requests an
alternative value from its argument, which in turn results in a request
of an alternative value for &pos <- i
. Like tab(i)
,
reversible assignment has no alternative value, but does restore the former
value of its left operand when it gains control. It then fails and control
falls through to fail.
There is one subtle difference between the behavior of the procedure as
given above and that of the built-in function. Since subscripting returns
a variable to which assignment can be made, it is possible to use the procedure
above as follows:
tab(i) := s
thus replacing the substring of &subject
returned by tab(i)
and also setting &pos
to 1 (which always happens when the
value of &subject
is changed). On the other hand, the built-in
function tab(i)
does not allow such an assignment (although
a feature of this kind is included in Version 3 of Icon). This extra feature
of the procedure above can be removed by explicitly dereferencing the argument
of suspend
so that only a value is returned. One way to do
this is by means of explicit type conversion which actually does nothing
but dereference the argument, since its type is already string
):
suspend string(&subject[&pos,&pos <- i])
As a final note. it should be observed that expressions such as
tab(i1 | i2)
work properly. This is a direct consequence of the goal-directed evaluation
mechanism of Icon, which causes a procedure to be called with alternative
arguments if the context in which the procedure call occurs results in requests
for alternative values. Thus
tab(i1 | i2)
is equivalent to
tab(i1) | tab(i2)
It is important to realize that this aspect of generation is completely
independent of the code in the procedure body for tab(i)
and
is solely a property of goal-directed evaluation.
Icon home page