[foo [bang bong bang bish box] [doobey dooley dongley] [ding bong bonce brinks [nog nag]]] ==> ** [foo [bang bong bang bish box] [doobey dooley dongley] [ding bong bonce brinks [nog nag]]]but
[foo [bang bong bang bish box] [doobey dooley dongley] [ding bong bonce brinks [nog nag]]] => ** [foo [bang bong bang bish box] [doobey dooley dongley] [ding bong bonce brinks [nog nag]]]We can print out values without the initial two asterisks using the `npr' procedure.
npr([This is a list]); [This is a list]We can print things out without POP-11 trying to put them onto a new line using the `pr' procedure.
pr([This is a list]); pr([This is another list]); [This is a list][This is another list]Using the `ppr' procedure we can print out lists without the enclosing list brackets or the preceding double-asterisk.
ppr([Look - no list brackets]); Look - no list bracketsSee HELP PRINT for an overview of other online files that deal with printing.
define get_numerals(n) -> list; vars i, list = [], numerals = [one two three four five six]; for i from 1 to n do [^^list ^(numerals(i))] -> list; endfor; enddefine; get_numerals(3) ==> ** [one two three]See HELP VARS for further details.
vars baz; define foo; vars dbong = 2; lvars lbong = 3; baz(); enddefine; define baz; dbong ==> lbong ==> enddefine; foo(); ** 2 ** <undef lbong>The value of `lbong' is not accessible inside the procedure `baz' because it is a lexical variable only accessible within `foo'.
HELP LEXICAL provides further information on `lvars'.
vars foo = 3; define baz; vars foo; foo ==> 5 -> foo; foo ==> enddefine; baz(); ** 3 ** 5 foo ==> ** 3Problems can arise when we are not sure whether the global variable we want to localize is a dynamic variable (set up with `vars`) or a lexical variable (set up with `lvars'). In this situation we should use the `dlocal' command to explicitly localize the variables. This command works out whether the relevant variable is dynamic or lexical and acts accordingly.
vars foo = 3; lvars baz = 2; define boo; dlocal foo = 1, baz = 2; foo ==> baz ==> enddefine; boo(); ** 1 ** 2For clarity, it is best to use `dlocal' whenever the aim is to localize a global variable. See HELP DLOCAL.
for x in [one two three] do x ==> endfor; ** one ** two ** threeHere we have a loop variable that gets set to the next element in the list each time around the loop.
for x on [one two three] do x ==> endfor; ** [one two three] ** [two three] ** [three]Here the loop variable is set to the next tail of the list each time around the loop.
for x to 5 do x ==> endfor; ** 1 ** 2 ** 3 ** 4 ** 5Here we have omitted the initial integer in the range. The default initial is `1' so the effect is as shown.
for x from 10 by -3 to -4 do x ==> endfor; ** 10 ** 7 ** 4 ** 1 ** -2Here we have specified a range of values for the loop variable that begins at 10 and then goes down in steps of 4 to the value -4.
All these variants are described in HELP FOR.
The `repeat' command is used to repeat a certain sequence of commands a certain number of times. Thus
repeat 4 times "hello" ==> endrepeat; ** hello ** hello ** hello ** helloWe can also use the `repeat' command to repeat a sequence of commands an infinite number of times. We do this by leaving out the `times' part in the command.
repeat "hello" ==> endrepeat; (This one goes on printing forever. Be prepared to hit the INTERRUPT key if you execute it.)See HELP REPEAT.
As an alternative to the while command we can use an `until' command. This continues to loop until the value of the provided expression is not `<false>'.
vars n = 1; until n > 5 do n + 1 ->> n ==> enduntil; ** 2 ** 3 ** 4 ** 5 ** 6See HELP UNTIL.
for x in [1 2 3 4 5] do if x = 4 then quitloop endif; x ==> endfor; ** 1 ** 2 ** 3There is also a `nextloop' command that causes control to jump back to the beginning of the next loop. Thus
for x in [1 2 3 4 5] do if x = 4 then nextloop endif; x ==> endfor; ** 1 ** 2 ** 3 ** 5There are conditional versions of both these commands. These are called `quitif' and `nextif'. They are always followed by a boolean expression in a pair of round brackets. The implement the relevant jump if the value of the expression is not `<false>'. Thus we might have written the previous example
for x in [1 2 3 4 5] do nextif(x = 4); x ==> endfor;All the loop control commands can be followed by an extra pair of round brackets containing a whole number. This causes control to jump to the relevant enclosing loop. Thus
for x in [1 2 3] do for y in [a b c] do nextif(y = "b")(2); [^x ^y] ==> endfor; endfor; ** [1 a] ** [2 a] ** [3 a]For a more extensive treatment of control commands see TEACH CONTROL.
if (lmember(3, [1 2 3 4 5]) ->> list) then list ==> endif;
If we know that a `matches' command will produce <true>, and all we are interested in is the side-effect that would be produced, we can use the --> command instead of matches. This is called the `matcher arrow'. As an illustration, consider the following rewrite of the code that works out accumulated costs from the phone_calls database.
vars name duration rate entry; [] -> costs_list; for n from 1 to 8 do phone_calls(n) --> [?name ?duration ?rate]; add_costs(name, duration * rate); endfor; costs_list ==> ** [[Jill 1.8] [Bob 1.75] [Fred 6.9]]Here, the matcher arrow is used to dig out the three components of each entry in the phone-calls database, and put them in special variables. This somewhat simplifies the code. Rather than seeing complex subscripting commands in the mathematical expression we see meaningful variable names. This makes it much easier to see what the code is doing.
Another useful packaging of the `matches' command is the `isin' operator. This is a boolean operator like `matches' itself but it tests whether a particular list pattern is in a list containing many sublists. Thus
[= ?x 3] isin [[a b c][one two three][1 2 3][foo bang][zog]] ==> ** <true>As a side-effect, `isin' assigns the sublist that matched the pattern to the built-in variable `it'. So
it ==> ** [1 2 3]See also HELP ISIN.
In addition to --> and `isin' we have a number of looping commands that use the matcher. Particularly useful is the `foreach' command. This is rather like the `for' command when used with a list. However, rather than taking a loop variable it takes a pattern. It executes one cycle of the loop for each time it is able to match the pattern against an element of the list. During that cycle, the element that matched is assigned to the variable `it'. An example illustrating this is as follows.
foreach [== ?word ?num] in [[uno one 1][due two 2][three 3][four 4]] do [The numeral for ^word is ^num] ==> endforeach; ** [The numeral for one is 1] ** [The numeral for two is 2] ** [The numeral for three is 3] ** [The numeral for four is 4]See also HELP FOREACH.
In addition to the facilities we have already looked at there is a whole package of matcher-based commands that work with a list called `database'. These are fully dealt with in TEACH DATABASE.
member ==> ** <procedure member>We can assign procedures to variables. Thus
vars pdr = member; pdr ==> ** <procedure member>We can also pass them into other procedures as arguments. In some situations this can be rather convenient. The built-in procedures `maplist' and `applist' exploit this facility directly. `maplist' takes a procedure and a list and it constructs a new list by applying the procedure to every item in the list. Thus
maplist([1 2 3 4], sqrt) ==> ** [1.0 1.41421 1.73205 2.0]The procedure `applist' works in a very similar manner except that it does not attempt to collect up the values into a list. Thus
applist([1 2 3 4], npr); 1 2 3 4The files HELP APPLIST and HELP MAPLIST provide additional information.
POP-11 also provides syntax that enables us to construct a new procedure by `freezing' certain arguments to an existing procedure. This is best illustrated using the `member' procedure. If we want to write a procedure that decides whether a particular object is an animal we might do it like this.
vars animals = [rat elephant dog cat snake horse]; define isanimal(thing) -> result; member(thing, animals) -> result; enddefine;As an alternative we can construct what is called a `closure' on member that freezes in a particular list argument. We do this by typing the procedure name followed by a pair of round brackets enclosing a paird of percents enclosing the argument we want to freeze-in. Thus
vars isanimal = member(%animals%); isanimal("rat") ==> ** <true> isanimal("house") ==> ** <false>See HELP CLOSURES.
Let me illustrate the job that the stack does with an example. Consider the following code.
vars x y z; 1; 2; 3 -> x -> y -> z;What is the value of `z' likely to be? The code looks rather strange at first sight. There is a fairly straightforward assignment in the middle (`3 -> x') but the rest is difficult to comprehend.
To correct read the code above we have to understand that when a value is produced by some POP-11 it is placed on the stack. The stack is just what it says it is. It is like a stack of dinner trays. With stacks of dinner trays you can only get trays on and off the stack in certain ways. You can push a tray on to the top. Or you can pull a tray off the top. The stack either pops up or gets pushed down. You cannot extract a tray from the middle of the stack.
The POP-11 stack is just like this. Every bit of code that produces a value effectively pushes that value onto the stack. Every assignment arrow takes a value off the stack and puts it into a variable (or some other updatable structure, see below). Thus the code
1; 2; 3;pushes three values onto the stack. The code
-> x -> y -> z;takes three values in succession off the stack. The first gets assigned to `x', the second to y and the third to `z'.
vars w1, w2, w3; "nothing" ->> w1 ->> w2 -> w3;We use the double-headed assignment arrow `->>' here to assign a copy in the first two cases.
The double-headed assignments are particular useful when used in conjunction with procedures that return some value or <false> if they are unable to exit successfully. An example of such a procedure is `lmember'. This is a built-in variant of the `member' procedure. It takes an item and a list and checks whether the item is a member of the list. If it is, the procedure returns that part of the list that begins with the item. if it is not, the procedure returns <false>. Thus
lmember("foo", [bang ding foo zog ting]) ==> ** [foo zog ting]But
lmember("foo", [bang ding zog ting]) ==> ** <false>Imagine that we have stored a list of names and ages in a list, like so.
vars ages; [fred 23 julie 34 ann 22 gerald 12] -> ages;We can then write a procedure called `get_age' that works like this.
define get_age(person) -> age; if (lmember(person, ages) ->> result) then result(2) -> age; endif; enddefine;Now
get_age("ann") ==> ** 22 get_age("julie") ==> ** 34and
get_age("chris") ==> ** <undef age>The task of explaining why we get this result here is set as an exercise below.
define silly(foo, ding) -> baz; foo -> baz; enddefine;When we call this procedure we pass in two arguments, thus.
silly(2, 3) ==> ** 2What really happens when we do this is that POP-11 puts all the input values on the stack, one after the other working right to left. It then calls the procedure, which pops the values off the stack and assigns them to the relevant input variables. This has the advantage that procedures can be defined so as to be able to take optional arguments. The built-in procedure `delete' is a case in point. This usually takes two arguments: an item and a list. It then returns the list with all instances of the item deleted. Thus
delete(1, [2 1 3 1 4 1 5 1 6]) ==> ** [2 3 4 5 6]However, we can also call it giving it an extra number argument at the end. If we give it 2 as final argument, the procedure will delete no more than 2 occurrences of the item. Thus
delete(1, [2 1 3 1 4 1 5 1 6], 2) ==> ** [2 3 4 1 5 1 6]When we define a procedure specifing explicit input variables then we are effectively telling POP-11 to set up the procedure so that it automatically takes values off the stack and assigns them to the corresponding input variables. If we leave out the input variables then POP-11 will not do anything. We can make the procedure then collect the input arguments explicitly. For example, we might define our own version of delete like so.
define new_delete -> newlist; vars i, item, list, num, n; -> list; if isnumber(list) then list -> num; -> list else 1 -> num; endif; -> item; 1 -> n; [] -> newlist; for i from 1 to length(list) do if list(i) /= item or n > num then [^^newlist ^(list(i))] -> newlist; endif; if list(i) = item then n + 1 -> n endif; endfor; enddefine; new_delete(1, [2 3 1 4 1 5 1 6], 2) ==> ** [2 3 4 5 1 6]
define good(x) -> y; x -> y; enddefine;is the same as
define naughty(x); x; enddefine;In fact, if you think about it, this has the same effect as
define naughty; enddefine;This version of the procedure does not even take its input argument off the stack. Therefore it is still there as a `result' when the procedure has finished.
Different programmers have different preferences about how procedures should return values. A common view is that the method that uses an explicit output variable is much better and should be used in all cases. However, you should be aware that the other methods are possible.
Another consequence of the fact that the values returned by procedures are effectively just values left on the stack is that procedures can return more than one result. This is demonstrated by the following.
define silly; 1; 2; 3; enddefine; silly() -> x -> y -> z; [^x ^y ^z] ==> ** [3 2 1]This sort of practice is deplorable since it leads to procedure definitions that are very difficult to read. However, there are situations where it is very useful to be able to return more than one value from a procedure. This can be achieved in a clean and respectable way by having multiple output variables. A version of the previous definition that uses multiple output variables is as follows.
define silly -> a -> b -> c; 1 -> c; 2 -> b; 3 -> a; enddefine; silly() -> x -> y -> z; [^x ^y ^z] ==> ** [3 2 1]This may look unduly complicated. However, when dealing with procedures containing large amounts of code, the use of explicit output variables is essential.
The fact that the stack plays such a central role in procedure calls and returns is one of the reasons why a very common mishap message in POP-11 is `STACK EMPTY'. This occurs when some bit of code attempts to take a value of the stack that is not actually there.
vars i, list; [] -> list; for i from 1 to 100 do [^^list ^i] -> list; endfor;We can also use a `for' loop that simply leaves all the number values on the stack. We can effectively collect these together into a list by using a hat in conjunction with a pair of round brackets.
[^(for i from 1 to 100 do i endfor)] -> list; list ==> ** [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100]For more on the stack see TEACH STACK.
When dealing with the various different types of data-object it is necessary to keep in mind the distinction between objects that are identically equal and objects that are just equal. We can test whether one value is exactly the same object as another value by using the test for identical equality `=='. Thus
vars x = [1 2 3], y = [1 2 3]; x = y ==> ** <true>but
x == y ==> ** <false>and
x == x ==> ** <true>In initializing `x' and `y' we used list constructor brackets. Thus we constructed two different lists with the same contents. In returning `<false>' the `==' is showing us that the two list are not the same object even though they contain the same components.
See also HELP EQUAL.
'This is a string that contain funny characters like &^%$#@())!]]]'If we print out this object, it comes out just the way we typed it in only without the quotes.
** This is a string that contain funny characters like &^%$#@())!]]]We can apply subscripts to strings in the normal way. Thus
'a string'(4) ==> ** 116Strings differ from words in the fact that they are always unique data objects. Words are effectively references into a central dictionary. Thus if we construct two words both containing the characters `abc' they will be identical objects. This is because they are both references to the same entry in the central word dictionary. Not so with strings. Each time we construct a string we construct a totally new object. Thus
vars w1 = "foo", w2 = "foo", s1 = 'foo', s2 = 'foo'; w1 == w2 ==> ** <true> s1 == s2 ==> ** <false>but
s1 = s2 ==> ** <true>See HELP STRINGS.
[1 2 3] >< "word1" >< 2 >< 'foobang' ==> ** [1 2 3]word12foobang
vars vec = {one two three}; vec(2) ==> ** twoThe hat symbols work in the usual way when used inside vectors. So
{the value of vec is ^vec} ==> ** {the value of vec is {one two three}}See HELP TWIDDLYBRA.
vars num_table = newassoc([[one 1][two 2][three 3]]); num_table("three") ==> ** 3The default value is <false>. So
num_table("five") ==> ** <false>We can update the table by assigning to the relevant `subscript'. So
5 -> num_table("five"); num_table("five") ==> ** 5In order to discover the value associated with a particular object in the table, we have to provide that exact object. It is not enough to provide an object that is equal to it. This can cause problems when using properties involving strings:
vars num_table = newassoc([['one' 1]]); num_table('one') ==> ** <false>The problem here is that the string we have constructed in the `subscripting' operation is a different object to the one we constructed when setting up the table.
See also HELP PROPS.
two([one two three four five one two three six]) ==> ** [one two] two([seven one two three four five two three six]) ==> ** [two three] two([seven two one three four five two three six]) ==> ** <false>
count_down(4); ** 4 ** 3 ** 2 ** 1 ** 0
intersect([tom dick harry],[mary susan jane]) ==> ** [] intersect([a b c d e f],[e f g a]) ==> ** [a e f]
check([a [b c [[e f]]g] h i]) => ** <false> check([a [b c [[e f]]5] h i]) => ** <true>
check([a [b c [[e f]]g] h i]) => ** [a [b c [[e f]] g] h i]) check([a [b c [[99 f]]5] h i]) => ** [a [b c [[censored f]] censored] h i]