Icon (programming Language) - Generators

Generators

Expressions in Icon often return a single value, for instance, x < 5 will evaluate and succeed if the value of x is less than 5 or fail. However several of the examples below rely on the fact that many expressions do not immediately return success or failure, returning values in the meantime. This drives the examples with every and to; every causes to to continue to return values until it fails.

This is a key concept in Icon, known as generators. Generators drive much of the loop functionality in the language, but do so more directly; the programmer does not write a loop and then pull out and compare values, Icon will do all of this for you.

Within the parlance of Icon, the evaluation of an expression or function results in a result sequence. A result sequence contains all the possible values that can be generated by the expression or function. When the result sequence is exhausted (e.g. there are no more values within the result sequence), the expression or function fails. Iteration over the result sequence is achieved either implicitly via Icon's goal directed evaluation or explicitly via the every clause.

Icon includes several generator-builders. The alternator syntax allows a series of items to be generated in sequence until one fails:

1 | "hello" | x < 5

can generate "1", "hello", and "5" if x is less than 5. Alternators can be read as "or" in many cases, for instance:

if y < (x | 5) then write("y=", y)

will write out the value of y if it is smaller than x or 5. Internally Icon checks every value from left to right until one succeeds or the list empties and it returns a failure. Remember that functions will not be called unless the calls within do not fail, so this example can be shortened to:

write("y=", (x | 5) > y)

Another simple generator is the to, which generates lists of integers; every write(1 to 10) will do exactly what it seems to. The bang syntax generates every item of a list; every write(!aString) will output each character of aString on a new line.

To demonstrate the power of this concept, consider string operations. Most languages include a function known as find or indexOf that returns the location of a string within another. Consider:

s = "All the world's a stage. And all the men and women merely players"; i = indexOf("the", s)

This code will return 4, the position of the first occurrence of the word "the". To get the next instance of "the" an alternate form must be used,

i = indexOf("the", s, 5)

the 5 at the end saying it should look from position 5 on. In order to extract all the occurrences of "the", a loop must be used...

s = "All the world's a stage. And all the men and women merely players"; i = indexOf("the", s) while i != -1 { write(i); i = indexOf("the", s, i+1); }

Under Icon the find function is a generator, and will return the next instance of the string each time it is resumed before finally failing after it passes the end of the string. The same code under Icon can be written:

s := "All the world's a stage. And all the men and women merely players" every write(find("the",s))

find will return the index of the next instance of "the" each time it is resumed by every, eventually passing the end of the string and failing. As in the prior example, this will cause write to fail, and the (one-line) every loop to exit.

Of course there are times where you deliberately want to find a string after some point in input, for instance, you might be scanning a text file containing data in multiple columns. Goal-directed execution works here as well, and can be used this way:

write(5 < find("the", s))

The position will only be returned if "the" appears after position 5, the comparison will fail otherwise, passing that failure to write as before. There is one small "trick" to this code that needs to be considered: comparisons return the right hand result, so it is important to put the find on the right hand side of the comparison. If the 5 were placed on the right, 5 would be written.

Icon adds several control structures for looping through generators. The every operator is similar to while, looping through every item returned by a generator and exiting on failure:

every k := i to j do write(someFunction(k))

Why use every instead of a while loop in this case? Because while re-evaluates the first result, but every produces all results. The every syntax actually injects values into the function in a fashion similar to blocks under Smalltalk. For instance, the above loop can be re-written this way:

every write(someFunction(i to j))

Users can build new generators easily using the suspend keyword:

procedure findOnlyOdd(pattern, theString) every i := find(pattern, theString) do if i % 2 = 1 then suspend i end

This example loops over theString using find to look for pattern. When one is found, and the position is odd, the location is returned from the function with suspend. Unlike return, suspend writes down where it is in the internal generators as well, allowing it to pick up where it left off on the next iteration.

Read more about this topic:  Icon (programming Language)