The first strategy is simply to exploit the interactive nature of the interpreters. Define small functions and test them interactively before going on to more complicated functions. Be sure you know how to cut and paste between your editor and terminal windows. Emacs users should find M-x shell helpful. For larger groups of functions, you can exploit the use primitive.
Many problems with Lisp and Scheme arise from dynamic typing and from the requirement that every data structure be represented using lists. We'll see how ML eliminates these problems through its static polymorphic type inference, but meanwhile, you can use run-time type tags. Let's say, for example, that you want certain lists to represent edge lists in a graph. Instead of just using car, cdr, and similar functions, you can add tags to the representation and define versions of functions that only work with the tagged representations:
(define mk-edgelist (l) (cons l 'edgelist)) (define un-edgelist (p) (if (= (cdr p) 'edgelist) (car p) (error (list2 'bogus:expected-edgelist,got p)))) (define edgelist-car (l) (car (un-edgelist l))) (define edgelist-cdr (l) (mk-edgelist (cdr (un-edgelist l)))) (define edgelist-cons (e l) (mk-edgelist (cons e (un-edgelist l)))) (define edgelist-null? (l) (null? (un-edgelist l)))If we define similar functions for node lists, we can, for example, produce the successors of a node:
(define successors (node edges) (if (edgelist-null? edges) (mk-nodelist '()) (let ((p (edgelist-car edges)) (rest (successors node (edgelist-cdr edges)))) (if (= node (car p)) (nodelist-cons (car (cdr p)) rest) rest))))and we can use the function as follows:
-> (successors 'a (mk-edgelist '((a b) (b c) (c d) (a d)))) ((b d) . nodelist)If we screw up, for example, we try to apply successors to a straight list, we get a suitable error message:
-> (successors 'a '((a b) (b c) (c d) (a d))) error: error: (bogus:expected-edgelist,got ((a b) (b c) (c d) (a d)))