TEACH FOREACH A. Sloman Nov 1982 Updated to use "!" October 1996 This file introduces the following looping syntax for doing something with all items in the database that match a given pattern: foreach do endforeach or foreach ! do endforeach CONTENTS - (Use g to access required sections) -- Prerequisites for this file -- What is foreach for? -- An example database -- The procedure present finds one thing only -- foreach iterates selectively over the whole database -- The syntax of foreach -- Where do the men live? -- Making a list of information from database -- How allof works -- Exercise define allof2 -- An alternative formulation, using [% ... %] -- Exercise: Finding the occupants of a town DOING SOMETHING WITH A SET OF DATABASE ITEMS ============================================ -- Prerequisites for this file ---------------------------------------- This file assumes that you are familiar with the use of MATCHES to compare a list and a pattern (e.g. see TEACH * MATCHES), and that you already know that the Pop-11 database is a list of lists that can be searched using procedures based on the matcher (as in TEACH * DATABASE). You should be familiar with the role of the variable prefixes "?" and "??" in patterns, to SET the value of a variable, and the role of the single and double uparrows "^" and "^^" to USE the existing value of a variable. (See TEACH * ARROW.) As explained in TEACH * MATCHES, if you define a procedure that uses a pattern containing pattern variables preceded by "?" or "??", then you should use "!" in front of the pattern, so that the pattern variables can be used as lvars, reducing the risk of unwanted interactions between procedures using the same variables. -- What is foreach for? ----------------------------------------------- Suppose you have a collection of facts stored in the database about people, their relations, their jobs, their possessions, etc. You can use a pattern to select items from the database. E.g. [?person isa teacher] would match database items such as [suzie isa teacher] [john isa teacher] If you wish to find only one item matching the pattern then you can use present or lookup (explained in TEACH * DATABASE). If you wish to find all the matching items, e.g. all the people who are teachers, then you can use foreach, e.g. using a form like this, which will perform some action on each person recorded as a teacher. foreach [?person isa teacher] do ....person.... endforeach; or, inside a procedure definition, using the "!" prefix foreach ! [?person isa teacher] do ....person.... endforeach; Note the occurrence of "?" before person means that person is a pattern variable. It will therefore need to be declared as a variable. Outside a procedure definition you can use vars person; Inside a procedure definition use "lvars". -- An example database ------------------------------------------------ Lets demonstrate with a database of information about some people. You may find it convenient to put the following examples into a file of your own called something like database.p, or foreach.p. You can delete the file later on. Insert this procedure definition: define people(); ;;; clear the database and set up a collection of "initial" facts [] -> database; add([joe isa man]); add([jill isa woman]); add([joe lives_in london]); add([jill lives_in brighton]); add([bill isa man]); add([sue isa woman]); add([bill lives_in london]); add([sue lives_in paris]); enddefine; You could extend this procedure with more examples of the same general kind, if you wish. Note 1. We could use "is a" as two separate words, and "lives in" as two separate words. That would certainly work, but would require more storage space, and would slightly slow down searching the database. Note 2. Instead of all those separate add() commands you can use a single command calling the procedure alladd with a list of lists, e.g. alladd([ [joe isa man] [jill isa woman] .... [sue lives_in paris]]) We'll use this procedure every time we want to start off with a new database. Try it out: ;;; Empty the database [] -> database; database ==> ;;; Initialise the database. people(); database ==> -- The procedure present finds one thing only ------------------------- We have information about several women in the database, but if you do vars x; present([??x isa woman])=> x => and then do it again present([??x isa woman])=> x => you'll see that it always finds the same thing (provided the database has not changed in between). That's fine if you just want to get the name of any one woman. But suppose you want to find the names of all of them? -- foreach iterates selectively over the whole database --------------------- The POP11 syntax word 'FOREACH' can be used to solve the problem. Try the following: vars x; foreach [??x isa woman] in database do x => endforeach; Every time the pattern matches a database item, the instruction "x=>" in the body of the loop will be obeyed. (This is called a loop because it does the same instruction repeatedly, though with a different value for "x" each time.) Inside a procedure definition you would do this lvars x; foreach ! [??x isa woman] in database do x => endforeach; -- The syntax of foreach ------------------------------------------ FOREACH is used in the format: FOREACH IN DO ENDFOREACH It uses MATCHES to compare each of the lists against the , and every time the match is successful it does the . When the is the DATABASE, you can leave out the 'IN....' bit. E.g. try foreach [??x isa woman] do x => endforeach; I.e. since you don't say in WHAT list of lists, it assumes you mean in the database. Try getting foreach to print out all the names of all the MEN. -- Where do the men live? ------------------------------------------------ Here is how you can make FOREACH print out the home towns of all the men ;;; set up the database with information about people people(); ;;; find where all the men live vars place, x; foreach [??x isa man] do lookup([^^x lives_in ??place]); [the home of ^x is ^^place] => endforeach; Try that. Then try doing the same for all the women. Then try printing out all the people who live in london. See what difference it makes if you replace "^x" with "^^x" in the last action. Also see what difference it makes if you use a single query in the pattern: vars place, x; foreach [?x isa man] do lookup([^x lives_in ??place]); [the home of ^x is ^^place] => endforeach; Why did changing the "??x" to "?x" mean that "^^x" had to be changed to "^x" ?d (If you can't answer that, ask for help. Understanding it is very important in using the pattern matcher and the database). You can mark and load the above instruction and it should work, after you have run the people procedure. However, if the above instruction were used inside a procedure definition, you could use "!" in front of all the patterns, without the "vars" declaration. Look closely at the above and decide which list expressions are patterns that need to be preceded by "!". Then compare your decision with the occurrences in this procedure: define where_men(); ;;; find where all the men live ;;; declare the pattern variables lvars x, place; foreach ! [?x isa man] do lookup( ! [^x lives_in ??place]); [the home of ^x is ^^place] => endforeach; enddefine; Compile that and then test it where_men(); Can you generalise that to define a procedure that takes either the word "man" or the word "woman" and prints out where all the instances live. define where_live(type); ;;; Find everything that isa type and print out where they live [The home of each ^type] => ;;; declare pattern variables lvars x, place; foreach ! [?x isa ... ] do lookup( ! [^x lives_in ??place]); [the home of ^x is ^^place] => endforeach; enddefine; What should replace the "..." in the foreach line? Why? Test this after making sure the people database is set up: people(); where_live("man"); where_live("woman"); -- Making a list of information from database ---------------------------- So far all our FOREACH loops have merely printed something out each time. Suppose we wanted a procedure which could make a list of all the women, or a list of all the men. We'd need to go through the database as before searching for things matching a certain pattern. But each time we found the appropriate item, instead of printing it out, we can add it to a list. We'll call our procedure ALLOF. It will take a list like [man] or [woman], i.e. a list containing what can follow 'isa' in a database entry. And it will use that to find corresponding individuals. Foreach such individual, it will add it to a list, which is finally to be returned as a result. Thus: define allof(type) -> out; ;;; type is a list , e.g. [man] or [woman]. ;;; out, the output variable, will be given a list of words. ;;; Start out as the empty list, then build it up [] -> out; ;;; pattern variable lvars person; foreach ! [?person isa ^^type] do [^person ^^out] -> out endforeach; enddefine; Type that into your test file and try it out: allof([man]) => allof([woman]) => It's a good idea to put test commands inside comment brackets, thus: /* allof([man]) => allof([woman]) => */ -- How allof works ------------------------------------------------- To understand this procedure you need to remember how to build lists (e.g. as in TEACH ARROW). The line [] -> out; initialises the variable OUT to contain an empty list. Then each time an item is found in the database matching the pattern ! [?person isa ^^type] we add the value of PERSON to all the things already in OUT [^person ^^out] and then assign the new list to be the new value of OUT -> out When ENDDEFINE is reached, i.e. the procedure finishes running, then the value of OUT is left on the stack as the result of the procedure. By then the value of out should be a list of words. In some databases it could be an empty list, if nothing was found. In that case the body of the loop would never run. Check that, as follows: /* allof([cat]) => allof([dog]) => */ Look back at the procedure and make sure you can see how this description fits it. -- Exercise define allof2 --------------------------------------------- How would you have to change that procedure to make it take the word "man" or the word "woman" instead of the lists? Try changing the definition so that it defines a procedure called allof2, which takes a WORD rather than a LIST as input. What should replace the "...." below, and why? define allof2(type) -> out; ;;; type is a word, e.g. "man" or "woman". ;;; out, the output variable, will be given a list of words. lvars person; [] -> out; foreach ! [?person isa ...] do [^person ^^out] -> out endforeach; enddefine; Test it /* people(); ;;; defined above allof2("man") => allof2("woman") => allof2("mouse") => */ -- An alternative formulation, using [% ... %] ------------------------ Pop-11 allows the percent symbol to be used in list brackets to collect together into a list all the things left on the "user stack" in an instruction. E.g. try compiling this, or something similar (getting all the commas and spaces right): [% 3, 4, 5 + 6 %] => [% repeat 6 times [cat on mat] endrepeat %] => In each case, the instructions between the percent symbols will be obeyed, and things will be left on the stack, but [ .... ] will collect them into a list. We can use this to redefine ALLOF so that it doesn't have to explicitly build a bit of the list each time round. Instead, we put the foreach expression between "%....%" inside list brackets, and let the body of foreach put what's been found on the stack, each time. Call the new version ALLOF3: define allof3(type) -> out; ;;; Type is a word. Out is a list of words, i.e. names of persons lvars person; [% foreach ! [?person isa ^type] do person endforeach %] -> out enddefine; I.e. the "foreach ... endforeach" loop between % ... % contains instructions which will put different values of PERSON on the stack, and at the end the square brackets will make a list, which is then assigned to OUT. Compare this version with the previous one. Which you use is a matter of taste. The only difference is that in the final list the items will be in a different order from the previous version. Test this. /* people(); ;;; defined above allof3("man") => allof3("woman") => allof3("mouse") => */ -- Exercise: Finding the occupants of a town -------------------------- Try to use the ideas just illustrated to define a procedure called "occupants" which takes the name of a town, e.g. a word like "london" or "brighton", and then makes a list of names of all the people who 'lives_in' the town. define occupants(place) -> list; ;;; place is a word naming a place. Return a list of words ;;; naming people who live at the place. ........ enddefine; Use a pattern something like [?person lives_in ^place] in the FOREACH loop. Test your procedure: /* occupants("london")=> occupants("brighton")=> occupants("berlin")=> */ -------------------------------------------------------------------- If you forget the format for FOREACH and you want a reminder later, you can look at HELP * FOREACH, which gives a summary. Further examples of the use of FOREACH can be found in TEACH * INFECT A related facility is explained in HELP * WHICH HELP * FOREVERY describes forevery, a generalisation of foreach, as it takes a list of patterns, not just one pattern, and finds all possible ways of consistently matching items from the list with something in the database. --- $poplocal/local/teach/foreach --- Copyright University of Birmingham 1996. All rights reserved. ------