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