CS 395 Behavior-based robotics, Fall 2001

Individual Assignment 1: GRL Practice

Out: Monday, October 7
Due: Wednesday, October 16, in class, on paper

This assignment with give you some practice thinking about GRL code.  Many of the questions are about debugging and many of those questions are not really specific to GRL, so hopefully it will be of some use to you beyond this class.

How GRL works

When you type GRL code into Scheme, it creates a set of data structures to represent the signals that you will want to have at compile time.  These data structures get bound to variables in Scheme so that you can work with them.  The define-signal macro gets translated into equivalent Scheme code to create the signal data structure and bind it to the specified Scheme variable.  Thus (define-signal a (+ b c)) gets translated into something roughly like:

(define a (make-a-signal + b c))

 Make-a-signal then just creates the data structure and stores the procedure + and the input signals b and c inside of it.  Notice that + doesn't actually get called; it just gets saved for the compiler to look at later on so that it knows that a should get updated by taking the sum of b and c.  Note that this assumes that the Scheme variables b and c already exist and are already bound to other signal data structures.  If you ask the Scheme interpreter what's in the variable a now, it will say that it's a signal:

> a
'#{Primitive-signal 204 a}

The Scheme interpreter prints signals as #{Primitive-signal  serial-number  name}.   Sometimes it will put something besides "primitive-signal" in, like "signal-group", but in any case, it will be clear that it's a signal.

The GRL compiler is a Scheme procedure that gets called with the signal data structures as arguments.  It computes C, Scheme, or BASIC source code that can emulate the network of signals you've specified.  Note that if we compile the signal a, then the + procedure still isn't executed; it's just placed in the output code for execution at run-time.  The one exception to this is if b and c are both constants.  In that case, the compiler will look up the values of b and c, add them, and mark a as being a constant too with the sum as a value.

Try-signals is a procedure (a macro, really) that calls the GRL compiler and then passes its result to the Scheme compiler for final compilation and execution.  If you did (try-signals a), then this would be when the + procedure would finally be executed: at run-time, after the signals are compiled.

If you write GRL code that has procedure definitions in it, then those definitions also get saved for the compiler.  The code:

(define-signal (sgn x)
  (cond ((> x 0)
         1)
        ((= x 0)
         0)
        (else
         -1)))

Procedures a data structure called a signal procedure.  Again, define-signal gets expanded into a normal Scheme define, with a magic data structure (the signal procedure) as a value.  If you ask the Scheme interpreter what's in the variable sgn now, it will say that it's a signal:

> sgn
'#{Signal-procedure sgn}

Signal procedures get run inside of the compiler.  They are called with signals as arguments and make new, bigger, signals to return as results.  Thus if you say:

(define-signal a (sgn (* b c)))

The compiler will call sgn with a new signal, temp-*, whose value is the product of b and c,  and sgn will return a new signal computed from temp-*.  The result will then be as if you had typed:

(define-signal temp-* (* b c))
(define-signal a
  (cond ((> temp-* 0)
         1)
        ((= temp-* 0)
         0)
        (else
         -1)))

Except that the compiler reruns sgn every time a is compiled, so that if you redefine sgn, a will change automatically.  This process of replacing calls to signal procedures with their results at compile time is called signal expansion.  To see what the compiler expanded your code into, you can type (show-expansion).

Of course, it's possible for things to go wrong during compilation, resulting in errors that prevent the program from being run in the first place.  This is what the first group of questions is about.

Reasoning about error messages

  1. Suppose you enter the following code into a file and load it:
(define-signal rotation-error
  (- left-distance right-distance))
(define-signal translation-error
  (- center-distance stopping-distance))

(define-signal rotation-command
  (* rotate-gain rotation-error))
(define translation-command
  (* translation-gain translation-error))

(define-signal base-driver
  (drive-base (rt-vector rotation-command translation-command)))

When you try to load it, you get the error message:

Error: exception
       #f
       (* '#{Source-signal 300 translation-gain} '#{Primitive-signal 308 translation-error})

What this says is that an exception (i.e. a low-level error) was generated while calling the procedure * with the arguments #{Source-signal 300 translation-gain} and  #{Primitive-signal 308 translation-error}.  Explain what the bug in the code is, why it produced this particular error, and how to fix it.

  1. Now suppose you type:
(define rotation-error-sign (sgn rotation-error))

And before you even run the compiler, you are rewarded with another error:

Error: attempt to call a non-procedure
       ('#{Signal-procedure sgn} '#{Source-signal 305 center-distance})

which says that you tries to call something other than a procedure, namely sgn, was called with the argument center-distance.  Again, explain the error and the fix for it.

  1. OK, now suppose you try the following program:
(define-signal rotation-error
  (- left-distance right-distance))
(define-signal translation-error
  (- center-distance stopping-distance))

;; The robot is "stuck" if there's less than 40 units of space in front of it
(define-signal stuck?
  (< center-distance 40))

(define-signal rotation-command
  (* (if stuck?
         rotate-gain
         (* 4 rotate-gain))
     rotation-error))
(define-signal translation-command
  (* (* (sgn stuck?)
        translation-gain)
     translation-error))

(define-signal base-driver
  (drive-base (rt-vector rotation-command translation-command)))

This program loads fine, but when you compile it, you get the error message:

An error occurred while compiling.
Signal name: "temp-<"
UID: 326
Source code: 
  ((sgn stuck?))
    -> ((sgn stuck?))
    -> (cond ((< x 0) -1)
             ((= x 0) 0)
             (else -1))
    -> (if (< x 0)
           -1
           (cond ((= x 0) 0) (else -1)))
    -> (< x 0)

Compiler subgoal stack:
   Inferring type of #{Primitive-signal 326 "temp-<"}

Error: Not a numeric type
       boolean

The first part of this message tells you that the error occurred while it was trying to compile the signal "temp-<", which is one of those funny internal signals that the compiler generates to represent subexpressions like the "temp-*" in the example in the previous section.  The "source code" section tells you that it was trying to compile:

(sgn stuck?)

which expanded to:

(cond ((< x 0)
       -1)
      ((= x 0)
       0)
      (else
       -1)

which expanded in turn into:

(if (< x 0)
    -1
    (cond ((= x 0)
           0)
          (else
           -1)))

(This is because cond gets expanded into a chain of ifs; we're seeing it after the first step of that transformation, but before it's been completely transformed).  In order to compile this if, it had to compile:

(< x 0)

where x, the local variable of sgn, has been bound to stuck?.  At this point, the error was generated.

The "compiler subgoal stack" message is telling you that when the error occurred it was trying to determine the type of the signal temp-<, which presumably is the (< x 0) signal.  The last part of the error report tells you that the error was that something was a Boolean that should have been a number.

Explain what bug caused the error message and where the bug appears in the original GRL source code.

Bad code

Some of the following code fragments are either buggy or badly designed in some way.  For each fragment, state whether it is ok, could be improved, or is actively buggy.  If it can be improved, state what the problem is and how to fix it.  If it is buggy, explain what error would be generated, why, and when (i.e. when you enter the code, when you compile it, or when it's running).

Assume that the definitions in the code above have already been entered and that any previously unmentioned signals, such as desired-running-time, have already been defined in some appropriate and correct manner.

Assume that drive-base has been defined to take an argument of type rt-vector (rotate/translate vector), and that slow-motion-vector and fast-motion-vector are rt-vectors.

  1. (define-signal done?
      (if (> time-running desired-run-time)
          #t
          #f))
  2. (define-signal alternate-base-driver
      (rotation-command translation-command))
  3. (define-signal thingy 34.8)
  4. (define-signal translate-command
      (min (* translate-gain
              (- center-distance stopping-distance))
           max-translate-velocity))
  5. (define-signal alternate-base-driver
      (if in-open-space?
          (drive-base fast-motion-vector)
          (drive-base slow-motion-vector)))
  6. (define-signal alternate-base-driver
      (drive-base (* (if in-open-space?
                         2.0
                         1.0)
                     slow-motion-vector)))
  7. (define-signal rotate-command?
      (* rotate-gain
         (sqrt (- left-distance right-distance))))

Turning it in

Write your answers down on a sheet of paper with your name on it.  Mark it "Individual Assignment 1" and turn it in in class on Wednesday.