These are tips specific to PLT Scheme. For guidelines that apply to Scheme and, by extension, to most programming languages, see Coding Best Practices.



Language Levels

If something in one of the Scheme texts doesn't seem to work in DrScheme, you may be at the wrong language level. The beginner levels treat certain forms of legal but advanced Scheme code as errors. Those forms are more likely mistakes that should be caught than code you want to run.

Try raising the level to Intermediate or Advanced. If the error goes away, that was the problem. But then think about whether you really need that advanced bit of Scheme, especially if this is for an exercise marked as doable in Beginner.

The operations are available at each level are described here.

If you pick Module, then you are in pure Scheme mode.


Tracing

If you can't figure out why your code is not working, trace one or more of your functions to see what's happening. To do this,


Strings and Characters

Both texts talk very little about strings and characters in Scheme. Here are the important basics. For more details, see the strings and characters sections of the PLT Scheme manual.

There is a function string-ith that is similar to string-ref, except that it returns a string of one character. Only use this when combining values with string-append. Use string-ref when analyzing the individual characters in a string.


PLT Scheme's added names

To make PLT Scheme a little less mysterious, the PLT developers added a number of more intuitive names for standard Scheme functions. The table below shows the names in standard Scheme, which are used in SICP, and the alternatives used in HTDP for PLT Scheme. Most standard names are also available in PLT Scheme, though not at all language levels.

Standard SchemePLT Scheme
carfirst
cdrrest
cadrsecond
nilempty
null?empty?

Nested Define's

SICP makes frequent use of nested define forms. This is not available at all in the Beginner level of PLT Scheme. Starting at the Intermediate level, you can nest define forms, but you need to use a local form. So, where SICP does this:

(define (sqrt x)
  (define (good-enough? guess)
    (< (abs (- (square guess) x)) 0.001))
  (define (improve guess)
    (average guess (/ x guess)))
  (define (sqrt-iter guess)
    (if (good-enough? guess)
        guess
        (sqrt-iter (improve guess))))
  (sqrt-iter 1.0))

in PLT Scheme Intermediate or above, you would do this:

(define (sqrt x)
  (local ((define (good-enough? guess)
            (< (abs (- (square guess) x)) 0.001))
          (define (improve guess)
            (average guess (/ x guess)))
          (define (sqrt-iter guess)
            (if (good-enough? guess)
                guess
                (sqrt-iter (improve guess)))))
    (sqrt-iter 1.0))

In PLT Scheme Module, you could write the form as shown in SICP.


Big-bang and event-driven programming

Much of the code you write involves functions called by other functions, e.g., (sum-squares x y) contains code that calls (square x) and (square y). Nothing happens until you call some function to start everything.

That's not how most of the programs you use today work. When you browse the web, you don't type (browse "www.google.com") to go to Google. When you edit a document, you don't type (delete-chars 45 12 23) to delete characters 12 through 23 on line 45. Something like those functions exist, but they get called when you do something like click on a link or highlight some text and hit the delete key. Most programs in everyday use are event-driven programs.

The way such programs work is that a windowing operating system (OS), like Windows, MacOS X, or X Windows, constantly runs in the background. It's in an infinite loop but a good one. Application programs, i.e., the programs you write, are also running. They tell the OS to draw windows on the screen, and they tell the OS which functions to call when events happen, such as key presses, mouse clicks, even the ticking of the internal clock.

When any of those events happen, the OS has to decide which function to call. For a mouse click, the OS gets the point on the screen where the mouse click occurred, checks to see which window for which application contains that point, and calls the mouse click function for that application. Then regular function call execution occurs in that program.

For a key press, the OS looks to see which window for which application is "in front" of the rest, and calls the key press function for that application.

In PLT Scheme, the function big-bang is used to tell the OS what functions to call for various events. Similar facilities are available in other languages, though few are anywhere near as simple.


Basic List Recursion

Using recursion to scan over lists is very very common. There are several examples of this in SICP 2.2.1. Look in particular at the definitions of length and append. Notice the similarity with their definition of factorial.

(define (factorial n)
  (if (= n 1)
      1
      (* n
         (factorial (- n 1)))))
(define (length items)
  (if (null? items)
      0
      (+ 1
         (length (cdr items)))))
(define (append list1 list2)
  (if (null? list1)
      list2
      (cons (car list1)
            (append (cdr list1) list2))))

There are four key commonalities:

The table below shows these parts for the three recursive example definitions:

Stepfactlengthappend
base case test(= n 1)(null? items)(null? list1)
base case value10list2
"add" form(* n ...)(+ 1 ...)(cons (car list1) ...)
"decrement"(- n 1)(cdr items)(cdr list1)

Most loops over lists will use cdr as the "decrement" operation, to go to the next part of the list.

Most loops that also construct a list for an answer will use

See PLT Scheme's added names for a list of alternative names for the above.

append and length are standard Scheme functions. You can't redefine them. If you want to try the above definitions, use other names, like my-length and my-append.


Inexact Numbers

PLT Scheme works very hard to keep numbers exact. Even though 0.3 is not representable exactly as a floating point binary number, PLT Scheme can represent it exactly using the rational 3/10. Some mathematical algorithms will generate fractions with larger and larger numerators and denominators, and this can dramatically slow things down. For example, given this definition:

(define (repeat-ave n x)
  (if (zero? n) x (repeat-ave (- n 1) (average x (/ 8 (* x x))))))

here are some timing results:

> (time (repeat-ave 5 1.0))
cpu time: 0 real time: 0 gc time: 0
1.9710425766479745180235900...
> (time (repeat-ave 10 1.0))
cpu time: 360 real time: 359 gc time: 0
2.0009314406381734468370272...
> (time (repeat-ave 11 1.0))
cpu time: 2984 real time: 2984 gc time: 0
1.9995349299633447853570538...

To avoid the use of fractions, use inexact numbers:

>(time (repeat-ave 11 #i1.0))
cpu time: 0 real time: 0 gc time: 0
#i1.9995349299633447

Pure Scheme Mode

If you select the Module language level, then you are working in pure Scheme. This shouldn't be necessary in this class. If you want to do it, you need to start your file with

#lang scheme
...

No PLT libraries of any kind are preloaded. If you want to use check-expect, you need to start your file with this:

#lang scheme
(require test-engine/scheme-tests)
...

Use the PLT Manual Search Engine to find out what libraries your favorite functions are in.


PLT Streams

If you want to play with lazy lists, called streams in SICP, then you need to put this at the front of your code:

(require srfi/41)
...

SRFI stands for Scheme Request for Implementation. SRFI's in Scheme define standard behaviors for libraries. They are similar to RFP's (Request for Proposal) in internet standards. Like RFP's, they are identified by number. SRFI 41 is the standard for Scheme streams.

SRFI 41 has many more functions than described in SICP, and differs in a few ways. Most critically,


Mutable Pairs

There are two ways to get mutable lists. One is to use the mutable pairs library.

(require scheme/mpair)

This defines mcar, mcdr, set-mcar!, set-mcdr!, mcons, mlist, mappend, and so on. It keeps the two kinds of cons pairs separate, so your code can use immutable pairs (the default) or mutable, if needed.

The downside of this is you have to rewrite (albeit only a little) any code that uses mutable lists.

If you want to leave the code as is, but still use set-car! and set-cdr!, you need

(require r5rs)

This library redefines all the standard Scheme list functions to use the mutable functions.

Caution:Neither library redefines functions that are in PLT Scheme but not standard Scheme. In particular, build-list is not redefined, so

(car (build-list 5 identity))

breaks in r5r2, because car is now really mcar, which needs an mcons but build-list creates a cons, and

(set-mcar! (build-list 5 identity) 6)

breaks in scheme/mpair for the same reason.

To fix this in r5rs, you need to define your own build-list. This is a good exercise in simple list recursion. You do not need to call any special functions. Just use cons and the rest as always. The right thing will happen.

To fix this in scheme/mpair, define and use build-mlist.

Another function in the same boat is foldr.


Valid HTML 4.01 Strict