Pop-11 includes mechanisms for creating and using `dynamic lists', whose elements are created as needed by a generator procedure. This is an example of what is sometimes referred to as "lazy evaluation".
Dynamic lists are created using the (horribly named) pdtolist procedure (derived from "ProceDure TO LIST"). This procedure takes a "generator procedure" as argument and produces a dynamic list as result.
A generator procedure is a procedure that can be called repeatedly without any arguments and will produce a new result each time; and if it ever produces as its result the unique Pop-11 object termin, that signifies that there are no more items to be generated.
Here is a generator procedure that uses a private list and goes on forever generating even numbers, i.e. 0, 2, 4, 6, 8, 10, etc.
define gen_evens() -> next; ;;; create a local, constant, private list containing 0 ;;; the number will be changed each time the procedure is run. lconstant store = [0]; store(1) -> next; next + 2 -> store(1); enddefine; gen_evens() => ** 0 gen_evens() => ** 2 gen_evens(), gen_evens(), gen_evens() => ** 4 6 8We can use a procedure like gen_evens to create a conceptually "infinite" list, thus
vars gen_list = pdtolist(gen_evens);
If you try to print out gen_list, Pop-11 will only show the part of the "infinite" list that corresponds to how far the generator procedure has actually got. We can force it to generate the first N additional items by accessing the first N items of the list:
gen_list => ** [...]The dots indicate an unexpanded dynamic list.
gen_list(1) => ** 10 gen_list(2) => ** 12Now look at the list
gen_list => ** [10 12 ...]Part of it is "static", or has been "solidified". But part is still dynamic, waiting to be expanded, as shown by the three dots. We can move things on:
gen_list(15) => ** 38 gen_list => ** [10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 ...]
The potential of dynamic lists can be illustrated using the procedure gensym. This takes a word as input and each time it is invoked it produces a new word got by appending a number to the word, e.g.
;;; Make the gensym library procedure accessible uses gensym; gensym("cat"), gensym("dog"), gensym("cat"), gensym("dog") => ** cat1 dog1 cat2 dog2By partially applying gensym to a word we can create a generator. By applying pdtolist to the generate we make a dynamic list.
1 -> gensym("pig"); ;;; make sure it starts with pig1, pig2,... vars pig_gen = pdtolist(gensym(%"pig"%)); pig_gen => ** [...]The three dots show that the dynamic list is still unexpanded. We can force pig_gen to run the generator procedure three times, by asking for the third element of the list, after which it will print out as partially expanded:
pig_gen(3) => ** pig3 pig_gen => ** [pig1 pig2 pig3 ...]To restart gensym, use cleargensymproperty().
cleargensymproperty(); ;;; This will affect the contents of the dynamic list pig_gen(4) => ** pig1 pig_gen(10) => ** pig7 pig_gen => ** [pig1 pig2 pig3 pig1 pig2 pig3 pig4 pig5 pig6 pig7 ...]
From Poplog V14.5, the procedure gensym is superseded by the procedure gen_suffixed_word, with the same functionality, and the action
cleargensymproperty()is replaced by by
clearproperty(gen_suffixed_word_prop)See REF *gen_suffixed_word, after V14.5
The procedures front, back and destpair would not work on on a dynamic list as expected. Instead we need procedures that can check whether a list is dynamic or not, and if it is, then run the generator procedure if necessary to get another element of the list, then create a static initial portion of that list, leaving the generator on the end.
The procedures hd, tl and dest are designed to do exactly that. Otherwise they work like front, back, and destpair respectively.
dest([a b c]) => ** a [b c]So nearly all the general purpose procedures for operating on lists are implemented using hd and tl and dest rather than front and back and destpair, so that they can also work on dynamic lists.
An example of a procedure that will NOT work on dynamic lists is matches. There are also certain "fast" procedures, described in the files HELP EFFICIENCY and REF FASTPROCS, that operate on static lists but not on dynamic lists. Their use can lead to obscure errors if you suddenly start using dynamic lists!
Another case that has to be handled is the case of a dynamic list that is empty.
Let's define a procedure that produces nothing but termin.
define gen_nothing() -> result; termin -> result; enddefine; gen_nothing(), gen_nothing(), gen_nothing() => ** <termin> <termin> <termin>If used to create a dynamic list this will effectively create an empty list:
vars nothing_list = pdtolist(gen_nothing); nothing_list => ** [...]Now if you try to tell whether this is an empty list by comparing it with [], you'll get the wrong result:
nothing_list == [] => ** <false>The procedure null, however, recognizes both [], and this dynamic list as empty lists:
null([a b]) => ** <false> null([]) => ** <true> null(nothing_list) => ** <true>And it changes the internal representation of the dynamic list to tell the Pop-11 print routines that the generator is finished, so that now it prints AS IF it were the same as []
nothing_list => ** []But it is not
nothing_list == [] => ** <false>It's a null dynamic list.
So if you are going to sometimes use dynamic lists then define all your procedures to use hd, tl, and null rather than front, back and == [].
Readers interested in knowing how dynamic lists are actually represented should read the Poplog file REF LISTS. Essentially they depend on the fact that if the final pair in the chain of lists contains as its back a procedure rather than [], and a non-false front, then the list is taken to be dynamic. However if the generator procedure has already produced termin, then the representation is changed by adding a pair whose front is false and whose back is the generator procedure.
We can look at the contents of nothing_list by using the procedure destpair, which, when applied to a pair, produces its front and its back:
destpair(nothing_list) => ** <false> <procedure gen_nothing>By contrast, the procedure dest, which works on lists which may be
dynamic, and returns the hd and the tl of the list, will complain about an empty dynamic list:
dest(nothing_list) => ;;; MISHAP - NON-EMPTY LIST NEEDED ;;; INVOLVING: [] ;;; DOING : dest ...
Dynamic lists are often useful for mathematical purposes using number generators of various kinds. Another use is to represent a stream of input to the computer. In Pop-11 input devices are often "converted" to generator procedures (sometimes called "producers") which, each time they are called produce the next item of input (if it is ready, otherwise they usually wait).
The Pop-11 compiler, called "compile" (named "popval" in earlier versions of Pop-11 and Pop-2), is able to take a list of text items (consisting of words, strings, numbers, and possibly other objects) and cause them to be compiled into instructions for the computer.
This list is called "proglist" and is described in detail in the files REF PROGLIST and REF POPCOMPILE. It is possible for the list to be a simple static list, as in
compile([3 + 3 => ]); ** 6More usefully, when a file is being compiled the text items in the file are transformed into a dynamic list, which is then compiled. It would be possible in principle to read in the whole file and make a huge static list, then compile that, but usually that's not sensible as a lot of space will be wasted while the early parts of the file are being compiled.
It is also useful when Pop-11 is being used interactively to treat the terminal as if it were an infinite file of text items. This is done by creating a dynamic list containing the items that are being typed in. This is based on a generator procedure that returns an item at a time, which it obtains from a generator procedure (charin) that returns a character at a time from the terminal, each time it is applied. You can test it interactively by giving this command:
charin(),charin() =>It will prompt you for input. You can type a letter or number followed by the RETURN key. It will then print out two numbers, one being the ascii code for the letter or number, and the second being the ascii code for a newline, i.e. 10. (There is a separate character repeater for `raw' mode interaction used by the editor, which does not wait for you to type RETURN after typing another character.).
The item repeater for interactive Pop-11 is created by applying the system procedure incharitem to charin, thus:
incharitem(charin)This creates a new procedure, which is an item repeater. Each time the item repeater is run it consumes enough characters to make a complete text item, and it returns that item, and then the next time it is run it returns the next item, and so on.
The dynamic list constructor pdtolist, can be applied to this item repeater, and that will create a dynamic list of program text items. That, in fact, is what proglist is when programs are being compiled either from a file or from the terminal. I.e.
pdtolist(incharitem(charin)) -> proglist;Because proglist is a dynamic list, if the compiler is reading in an expression of some kind and the user has not finished typing it, the compiler simply waits till more is typed (every time the user presses the RETURN key, the items typed so far are made available as an extension to proglist).
Note that the above is highly modular: instead of compiling from the current input stream Pop-11 can compile from any sequence of characters, including the characters stored in a string, as illustrated by the following command, which uses stringin to turn a string into a character repeater:
compile(pdtolist(incharitem(stringin('66*33 =>')))); ** 2178You can experiment with an infinite list connected to the current input stream as follows:
vars inputlist = pdtolist(incharitem(charin)); inputlist => ** [...]
This example will work well only in the editor.
The following will prompt for input until you have typed enough for three full text items. Suppose you respond by typing "the cat" on one line, followed by RETURN, followed by "on" on the next line:
inputlist(3) => ** onYou can now print out the current state of inputlist:
inputlist => ** [the cat on ...]This will now prompt for two further input items:
inputlist(5) => inputlist =>For more information on dynamic lists and the compiler input stream see REF LISTS, and REF PROGLIST
For writing interactive programs it is not usually convenient to use charin and proglist. Generally it is safer for beginners to use the built in procedure readline, illustrated in TEACH RESPOND, which might be defined thus:
global vars pop_readline_prompt = '? '; define readline() -> list; lvars item, list, procedure rep = incharitem(charin); ;;; item repeater ;;; temporarily change two global variables. dlocal popnewline = true, ;;; make newlines recognizable popprompt = pop_readline_prompt; ;;; Make a list items to next newline [% until (rep() ->> item) == newline do item enduntil %] -> list; enddefine; ;;; Test it readline() => ? pretty polly pretty polly ** [pretty polly pretty polly] 'Please say something: ' -> pop_readline_prompt; readline() => Please say something: how are you today? ** [how are you today ?]In the last example the words after ":" were typed in, and made into a list.