TEACH POPCOURSE4 - Readline, matches and return

Contents


The need for `user interfaces'

In previous files we have seen we can put together sequences of POP-11 produce produce complex configurations of objects in the bugworld. Finding the code that produces a particular configuration can be a lengthy task. It would be far more convenient if we could interactively build up the configuration of objects, using some sort of `user interface' to the program. In this file we will look at how we can build a simple user interface to the bugworld by writing code that exploits a facility known as `the matcher'.


Getting user input with `readline'

Most programs require some sort of `user-interface', i.e., the ability to interact with a human user. The simplest approach in building such an interface is to assume that the interaction will be textual, i.e., based on the user and the program swapping bits of text. We have already seen how to print out text (we can do this using lists and the `==>'). So now we need a way to read in text from the user.

POP-11 provides a special command called `readline()' that gives us what we want. When executed, the `readline()' command prints out a query symbol at the left hand of the window and then waits for something to happen. If the user types something in and hits RETURN, the `readline()' command wakes up and produces as a value, the text the user typed in, in the form of a list. You can test this out by executing the following commands.

vars user_input;
readline() -> user_input;
An interaction with POP-11, following execution of the two lines above is shown below.

? this is some input
Following this, the value printed out by

user_input ==>
is

** [this is some input]
Note that the value produced by `readline()' is always a list.

The `readline()' command can be used for the purposes of producing greetings. For example

vars name;
[Hello. Please type in your name] ==>
readline() -> name;
[Hello there ^^name, please to meet you] ==>
Note the use of the double-hat to `get rid of' the unwanted list-brackets.

Experiment: Write a procedure called `sum' that uses `readline' to read in two numbers from the user. The procedure should then add the two numbers together and print out the result.


Using `matches' with the single query

When user-interfaces are constructed using `readline()', it is often necessary to find out whether the input received matches a particular pattern. For example, in a procedure that performs summation of two numbers (extracted from the user with `readline()'), we might need to be able to cope appropriately with a variety of different input formats; e.g.,

[add 3 to 7]
[do 56 + 92]
[sum 123 and 765]
The easiest way to do this in POP-11 is to use the `matches' command (also known as `the matcher'). In the simplest case we use this command in conjunction with the `if' command to find out whether two lists are the identical. For example,

vars input;
readline() -> input;
if input matches [add 3 to 7] then
   3 + 7 ==>
endif;
will print out the value of 3 + 7 only if you (the user) responded to the `readline()' prompt by typing in `add 3 to 7', because that is the only way that the value of `input' will be identical to [add 3 to 7].

Note the structure of this command. First we have the word `if', then we have the list we are interested in, then the word `matches' followed by the list we want to test against. Following this we have the word `then' followed by any number of valid commands, followed finally by the word `endif'. The commands between the `then' and the `endif' are only done if the first list matches the second.

In fact the two lists do not have to be identical for the match to `succeed'. The second list can be a pattern made up of of literal values and wild-cards. For example,

vars input, x1, x2, x3, answer;
readline() -> input;
if input matches [?x1 3 ?x2 7] then
   3 + 7 -> answer;
   [the answer is ^answer] ==>
endif;
will print out [the answer is 11] provided only that you respond to the prompt by typing in four items, the second and fourth of which are `3' and `7'. This happens because `matches' treats any item in the right-hand list that begins with `?' as a wild-card and allows it to be matched with anything in the other list. Thus

if [add 123 and 765 together] matches [?x1 123 ?x2 765 ?x3] then
   123 + 765 -> answer;
   [the answer is ^answer] ==>
endif;
produces

** [the answer is 888]
The names that appear after the queries in the pattern should be variable names, since, as a side effect, the matcher assigns to the wild-card variable the value that the wild-card matches against. Thus, after doing the command above, the values of x1, x2 and x3 are as follows.

x1 ==>
** add

x2 ==>
** and

x3 ==>
** together
Experiment: work out the values of `x', `y', `something' and `somethingelse' after we load the following?

vars x, y, something, somethingelse;
if [foo 2 bang] matches [?x 2 ?y] then
   [yes] ==>
endif;
if [foo 2 bang] matches [?something 2 ?somethingelse] then
    [no] ==>
endif;
Experiment: work out which of the following matches prints out [yes] and what are the values given to the variables `x' and `y' in each case?

vars x, y;
if [a b c] matches [a ?x c] then [yes] ==> endif;
if [the cat sat on the mat] matches [?x sat on the mat] then [yes] ==> endif;
if [[the cat] sat on the mat] matches [?y sat on the mat] then [yes] ==> endif;
if [] matches [?y] then [yes] ==> endif;
vars list;
[[a 1] [b 2] [c 3]] -> list;
if list matches [[a 1] [b ?x] ?y] then [yes] ==> endif;
Experiment: work out what property is shared by every list that matches the pattern [?x ?y ?z]?


Using `matches' with the double query

Sometimes we want patterns that will match lists of unknown length. Suppose we want to know if a list begins with the word `no' but don't care how many words there are after the `no'. The pattern `[no ?more]' cannot be used for this purpose since it only matches lists containing exactly two elements.

POP-11 provides another kind of wild-card which is a variable name preceded by a double query, e.g., `??x'. Double-query wild-cards can match against any sequence of elements in a list. So if we do

if [no cat on the mat] matches [no ??x] then [yes] ==> endif;
we get

** [yes]
and the value of the variable `x' is the list `[cat on the mat]'. Note the difference between single- and double-query wild-cards. With single-query wild-cards a single item is assigned to the wild-card variable. With double-query wild-cars, a list is assigned to the wild-card variable and this contains the whole sequence of items that were matched.

if [1 2 3 4 5 6 7 8 9] matches [?x 2 ??y] then [yes] ==> endif;
prints

** [yes]
and values of `x' and `y' are `1' and `[3 4 5 6 7 8 9]', respectively.

But

if [foo 2 3 4 5 6 7 8 9] matches [??x 2 ?y] then [yes] ==> endif;
prints nothing since the list does not match the pattern. There is a sequence of items after the 2 in the list and this cannot be matched against a single-query wild-card.

Experiment: find out what value query variables have if matching fails? For example, what is the value of `x' after we do

if [a b] matches [?x c] then [yes] ==> endif;
Experiment: Which of the following prints [yes] and why?

vars x, y, z;

if [a b c] matches [??x] then [yes] ==> endif;

if [] matches [??x] then [yes] ==> endif;

if [a b c] matches [a b c ??y] then [yes] ==> endif;

if [[a 1] [b 2] [c 3]] matches [??x [?y ?z]] then [yes] ==> endif;

if [David] matches [D ??x] then [yes] ==> endif;

Restricting `matches' with duplicate query variables

When executing an `if ... matches' command, POP-11 will only execute the commands if all query variables can be instantiated (i.e., assigned to) consistently. It will not allow a variable to be given one value at one point in the pattern and a different value later on. This can be exploited for testing special conditions in the list. For example, imagine we want to test whether a particular list has any duplicated items in it. One way to proceed is to use an `if ... matches' as follows.

vars x, y, z, item;
[1 2 3 4 5 6 7 3 8 9] -> list;

if list matches [??x ?item ??y ?item ??z] then
    [the item ^item is duplicated] ==>
endif;
When executed this code produces

** [the item 3 is duplicated]
We do not know in advance where the duplicate items will appear. However, by putting double-query variables (that match anything or nothing) on both sides of both single-query variables, we effectively take account of all possible positions that the duplicated items might appear in the list.


Using `matches' and `readline' together

Using the `matches' conventions in conjunction with `readline', we can write procedures that hold simple dialogues. Consider the following procedure:

define ask();
  vars answer, x, y;
  [Hello, what are your hobbies?] ==>
  readline() -> answer;
  if answer matches [??x music ??y] then
    [That is nice, I like all kinds of music] ==>
  else
    [Oh, I prefer to listen to music] ==>
  endif;
enddefine;
If we do `ask();' we get a dialogue such as this:

** [Hello , what are your hobbies ?]
? i like music
** [That is nice , I like all kinds of music]
Using a repeat loop, we can arrange for the `ask' procedure to build up a list of known hobbies. For example

vars hobbies;
[] -> hobbies;
define ask();
  vars answer, x, y;
  repeat 999 times
     [please type in one of your hobbies] ==>
     readline() -> answer;
     [^^answer ^^hobbies] -> hobbies;
     [yes, I like ^^answer too] ==>
  endrepeat;
enddefine;
Now the interaction goes on forever, or at least until we press the INTERRUPT key.

ask();
** [please type in one of your hobbies]
? golf
** [yes , I like golf too]
** [please type in one of your hobbies]
? sailing
** [yes , I like sailing too]
** [please type in one of your hobbies]
? balloons
** [yes , I like balloons too]
** [please type in one of your hobbies]
(INTERRUPT key pressed to terminate interaction)
Folling this interaction the list of hobbies is

hobbies ==>
[balloons sailing golf]
Experiment: work out why the list is effectively in reverse order.


POPBUGS ex: an interface for object-creation

We can adapt the `ask' procedure to create a simple user interface to the POPBUGS program. Note the user of the `repeat 999 times' to keep the interaction going after the first round.

vars answer, x, y, z;
define ask();
   repeat 999 times
      [Would you like to add a new object to the bugworld?] ==>
      readline() -> answer;
      if answer matches [??x yes ??y] then
         [What do you want to call it?] ==>
         readline() -> answer;
         [[name ^^answer]] -> pb_spec;
      endif;
   endrepeat;
enddefine;
To begin a dialigue with this procedure we give the command `ask()'.

ask();

** [Would you like to add a new object to the bugworld ?]
? yes
** [What do you want to call it ?]
? robert
** [Would you like to add a new object to the bugworld ?]
? no
** [Would you like to add a new object to the bugworld ?]
(INTERRUPT key pressed to terminate interaction)
As it stands the procedure is not particular helpful. However, we can easily make it more useful by adding in extra commands. For example, we could add in an extra `if' so as to enable the user to decide whether to create a bug or an obstacle. In POPBUGS, an object counts as a bug if it has a bug-like behaviour attribute. The simplest example is the word `pb_forwards'. (Bugs with this behavioural attribute move forwards one step in each cycle of the simulation.) The simplest example of a behaviour value for a non-bug is the word static. Thus we can define the new procedure thus.

define ask();
   repeat 999 times
      [Would you like to add a new object to the bugworld?] ==>
      readline() -> answer;
      if answer matches [??x yes ??y] then
         [What type of object do you want to add?] ==>
         readline() -> answer;
         if answer matches [??x bug ??y] then
            [[behaviour pb_forwards]] -> pb_spec;
            [added a new bug] ==>
         endif;
         if answer matches [??x obstacle ??y] then
            [[behaviour static]] -> pb_spec;
            [added a new obstacle] ==>
         endif;
      endif;
   endrepeat;
enddefine;
Now the interaction becomes (slightly) more interesting.

ask();
** [Would you like to add a new object to the bugworld ?]
? yes
** [What type of object do you want to add ?]
? a nice big bug
** [added a new bug]
** [Would you like to add a new object to the bugworld ?]
? yes
** [What type of object do you want to add ?]
? just an obstacle please
** [added a new obstacle]
** [Would you like to add a new object to the bugworld ?]
(INTERRUPT key pressed to terminate interaction)

Using `return' to jump out of a procedure

One problem with the use of `repeat' loops in interactive procedures is that the user has to cause an interrupt in order to finish the interaction. However, we can arrange for terminations to be handled more cleanly by using the `return' command. This command, which can only be used inside a procedure definition, causes the procedure to finish immediately without any of the commands which come after the `return' being done. A `return' command consists of just the word `return' followed by the usual, terminating semi-colon. So to add an exit route to interactive procedure we should add in an following `if' containing an embedded `return' command. For example

vars hobbies;
[] -> hobbies;
define ask();
  vars answer, x, y;
  repeat 999 times
     [please type in one of your hobbies] ==>
     readline() -> answer;
     if answer matches [bye] then
        return;
     endif;
     [^^answer ^^hobbies] -> hobbies;
     [yes, I like ^^answer too] ==>
  endrepeat;
enddefine;
With this procedure, the user can exit from the interaction by typing `bye'.


Tip of the day

The Ved command `gs /foo/baz' replaces every occurrence of the `foo' with `baz' in the current file. This is very useful when you want to change the name of a variable throughout a program.


Exercises

To test your understanding of the material in this file you should do the following exercises. First, copy them into a file of your own. Then, edit in answers underneath each question and print out the file. Answers which involve writing POP-11 code should include some explanatory text.

  1. What is the shortest match pattern that matches all of the following?

    [mary votes for the tories]
    [george votes the same way his father votes]
    [it takes a long time to count up the votes]
    
  2. What is the shortest match pattern that will match all of the following

    [the man that was here last night left his coat]
    [that was the best thing you ever did]
    [if I did not know better I would say that was good news]
    
    but not

    [the best thing you ever did was good news]
    
  3. Write a match pattern that will only match against a list that contains three repeated values. An example of such a list is [1 3 4 6 3 4 4 10]. This has three 4s.

  4. When POP-11 is matching a list such as [1 2 3] against a pattern containing a sequence of double-query variables (e.g. [??x ??y]) it has to decide which items from the list should be allocated to which query-variables. It could assign [] -> x and [1 2 3] -> y; or [1] -> to x and [2 3] -> y; or [1 2] -> x and [3] -> y, etc. What strategy does it use in situations like this? Describe the strategy in English.

  5. Write a procedure `calculate' that asks for the user to type in exactly four numbers and then prints out the total of the numbers typed in. Executing `calculate();' should produce something like this:

    ** [Please type in your numbers]
    ? 50 80 25 15
    ** [the total is: 170]
    
  6. Insert a pattern after `matches' in the code below which will (a) match the list and (b) cause one to be assigned to the variable `a', [two three] to be assigned to the variable `b', [five six] to be assigned to the variable `c'.

    [one two three four five six seven] matches
    
  7. Modify the `ask' procedure so as to enable the user to specify a colour for the new object. Remember that, in an assignment to pb_spec, a colour is specified by including a sublist of the form [colour red].

  8. Modify the `ask' procedure so as to enable the user to specify a position for the new object. In an assignment to `pb_spec', a position is specified by including a sublist such as [position [10 10]]. (Note that readline does not handle list brackets so it will not properly read in something like `dimensions [10 20]'.)

  9. Write a program based on the `ask' procedure that will enable the user to specify the values for a variable number attributes. (You may need to use more than one procedure.) The program should first invite the user to specify some attributes for a new object. It should then read in attribute/value pairs up until some pre-arranged word is typed, e.g., `ok'. The pairs should be collected up into a list and used to create a new object with the specified attributes. Note that possible attributes include `name', `shape' (circle, box, ant, triangle and tank), `colour' (red, green, blue, yellow, cyan, black), `trail_colour' (as for colour), `position' (a list of two numbers between 0 and 100), and `dimensions' (a list of two numbers between 2 and 10).

Revision exercises

Do not do the following exercises on your first reading of this file. Save them from revision purposes.


Quick reference

The following list contains the commands that have been introduced in this file. If you are keeping a file of commands for easy reference, you might want to add these items to that file.

readline()            command for obtaining input from the user
if ... matches ...    executes commands if one list matches the other
?                     matches wild-card that matches a single item
??                    matches wild-card that matches a sequence
return                causes a procedure to stop executing

Moving on

If you have completed all the exercises above then you are ready to move on to TEACH POPCOURSE5. You can do this by giving the command `teach popcourse4'.


Page created on: Fri Apr 26 09:34:52 BST 2002
Feedback to Chris Thornton
hits this year