Comp 210 Lab 3: Donkey, Style

Index: Donkey, How to print homeworks, Scheme style, Scheme Syntax


Donkey

Like DrScheme, Donkey is a programming environment for Scheme. While DrScheme is generally better, Donkey has one important feature that DrScheme lacks: it allows you to see how your program is evaluated. This lab shows you how to use that feature.

To run Donkey, either

(This assumes you have run register comp210. If not, you can enter /home/comp210/bin/donkey.)

As an example, type (if (= 1 2) (+ 1 2) (+ 1 3)) in the main window.

  1. Press the Return key, or equivalently select the Eval button. This evaluates the expression to 4, just like DrScheme.
  2. Put the cursor back on that expression by clicking in its box. This time select the Step button. Donkey highlights the first subexpression to evaluate, and up in the Next Reduction Rule textbox it displays the applicable rule for that subexpression. Selecting the Step button again evaluates that subexpression and goes on to the next subexpression to evaluate. Eventually, you evaluate the whole expression.

    When stepping, you also have other options, e.g., selecting the Unstep button goes backwards one step in the evaluation.

  3. Put the cursor back on the expression again. This time select the Step All button. This completely evaluates the expression, like the Eval button, but it builds up the extra information so that you can now Unstep, and then Step/Unstep like we just saw.

Since stepping through larger programs can be very tedious, you can indicate what kinds of subexpressions are "important" to show during stepping. In the Stop-at menu, currently All is selected, i.e., stepping stops at all subexpressions. If you select "if/cond/case", stepping will only stop and conditionals, but not at function applications.

The other main option you'll want in Donkey are to Load files (in the File menu). We assume you'll usually use DrScheme for writing your programs. Select Help from the Help menu to explain other options.

Try stepping through some examples. In particular, try a simple use of recursion:

(define length
   (lambda (l)
       (cond
           ((null? l) 0)
           (else      (+ 1 (length (cdr l)))))))
(length (list 1 4 8 2))
Observe how a bunch (hmm... a stack?) of additions pile up waiting for you to calculate the length of the rest of the list.

When stepping through recursive functions, you sometimes want to select only "apply" in the Stop-at menu, i.e., only indicate when it's applying a user-defined function and skip over the conditionals and primitive functions.


How to print homeworks

Students have asked how best to print their homeworks, including testing, for grading. We suggest the following:


Scheme style

Like any language, there are conventions for writing Scheme that help make it more readable. In this lab and the previous one, we've discussed some conventions on providing supplementary information in comments. In class, we briefly discussed conventions for choosing between cond and if. Here, we will discuss layout and indenting conventions. Also of note, programmers vary widely in naming conventions, e.g., whether to name a list of numbers as l, lon, AListOfNumbers, nums, or something else.

The basic goals of spacing and indentation in Scheme include

DrScheme will help you with the first two issues: selecting the Check Syntax button will highlight keywords, and DrScheme automatically aligns a line nicely when you use the Return key. The following are examples of good style.

(define afunction
   (lambda (x)
      body))
The define and lambda keywords are each on separate lines to point out that you are 1) making a definition and 2) creating a function of one variable.

(if (= 3 x)
    (f 5)
    (+ 7 y))
The "then" and "else" branches are on separate lines and aligned so that you can easily see them. The same idea holds for larger conditionals:
(cond
   ((null? l) 0)
   (else      (+ 1 (foo (cdr l)))))
(Note that I like to also align the result expressions in a cond, assuming the test expressions are reasonably short.)

When subexpressions are long, or so deeply nested that you're regularly using 60+ columns of characters, you may have to break lines more frequently, e.g.,

(cond
   ((null? l)
    0)
   (else
    (+ 1 (foo (cdr l)))))
or in function calls,
(myfunction (+ 3 (* 5 x) y)
            (- 3 (f 9 z)))

Note that using too many lines obscures structure:

(define
   afunction
   (lambda
      (l)
      (cond
         ((null? l)
          0)
         (else
          (+ 1
             (afunction
                (cdr l)))))))
It also makes you scroll vertically more.


Scheme Syntax

Let's go over Scheme's syntax again, with emphasis on the common problem of "where do parentheses go?".

Expressions can be constants, e.g.,

Expressions can be function applications:

Expressions can be special forms. Special forms are not function applications. In many languages, most expressions are special forms and thus are not "special". In Scheme, these are considered the exceptions to the general case of the function call. So far we've seen the following:

Looking at the above, parentheses can mean one of three things (given this subset of Scheme):

Parentheses always indicate one of these. They never mean "group things for readability" like standard math notation.