Differences between Scheme and Common Lisp

This section is intended for students who already know Scheme. We describe the most important differences between Scheme and Common Lisp.

Size of the language

Common Lisp is a large language compared to Scheme, and that is good. Many things in Common Lisp that are missing in Scheme, are features that are frequently needed by many programmers. Having these features standardized is a good thing, as opposed to having to add them, often in an ad-hoc way, for each application.

Some of these features include: packages, arrays, structures, hash tables, object-oriented programming features, a powerful macro feature for extending the language, generalized assignment, and much more.

Numbers

The Scheme specification allows an implementation of Scheme to have very little support for numbers. In fact, it would be acceptable to have only floating point numbers and still call it Scheme.

Common Lisp, by contracts, requires every implementation to support floating point numbers, rational numbers, integers of arbitrary precision, complex floating point number, and complex rational numbers. This fact makes programs that use numbers more portable.

Tail call optimization

In Scheme, tail calls must be optimized by the compiler, so that no additional stack space is consumed. Thus, in Scheme, iteration is a special case of tail recursion. You can count on the Scheme compiler (or interpreter) to convert tail calls to simple jumps.

No such rule exists in Common Lisp, although many Common Lisp compilers actually do optimize tail calls. It is therefore less frequent in Common Lisp to use tail recursion for (say) traversing lists. Instead, Common Lisp has a rich set of iteration primitives for exactly that purpose.

Program/data equivalence

A major problem with Scheme that does not exist in Common Lisp is that the Scheme language does not define an internal representation for programs, although to be fair, all Scheme implementations do.

For instance, in Scheme, there is a difference between:

  (define (f x y . (z))
    ...)
which is syntactically illegal, and:
  (define (f x y z)
    ...)
which syntactically legal. As data (if read by the read function) they are the same. Thus, Scheme does not require code to be read by the read function.

Common Lisp, on the other hand, does require two such forms to be equivalent. This feature is the very basis of the Common Lisp macro system, which uses Lisp data to represent and manipulate Lisp code.

Since no such equivalence exists in Scheme, one can argue that it is impossible to ever have a good macro system for Scheme. In practice, of course, nearly all implementations do define the equivalence and do contain macro systems.

Multiple namespaces

The difference that is perhaps the most difficult for students to get used to is the fact that Common Lisp global function names are different from names of other data objects. We say that Common Lisp has multiple name spaces whereas Scheme has a single name space.

The main idea in Common Lisp is that the name fun in an expression such as:

  (fun arg)
is treated differently from the name arg, simply because it is the first subexpression of a function call. Instead of using the normal value of the variable fun, its function value is used. In normal circumstances, this works the same as Scheme. However, as soon as something slightly more complicated is attempted, the difference is visible.

Perhaps the most obvious difference is that in Common Lisp, it is possible to use the name of a function as an ordinary value without shadowing the function. This is possible in Common Lisp, but not in Scheme:

  (let ((list '(1 2 3)))
    (list list))
In the expression (list list), the first occurrence of list refers to the built-in function of that name, and the second refers to the local variable created by let.

On the other hand, this is possible in Scheme but not in Common Lisp:

  (let ((fun (compute-a-function)))
    (fun x y))
Here, Common Lisp will search for a global function definition of fun (which probably does not exist), whereas in Scheme, the value of the local variable will be used. To do the same thing in Common Lisp, we need to use a built-in function called funcall like this:
  (let ((fun (compute-a-function)))
    (funcall fun x y))
Now, the name fun is in an argument position, so the value of the local variable is used and passed to funcall. The built-in function funcall then applies its first argument (as a function) to the rest, which is what was intended.

We have seen how Common Lisp through the built-in function funcall is able to call a function that is the value of an ordinary variable. We must also solve the inverse problem, i.e., obtain the function object that is not the value of an ordinary variable, but only the global function value of some name such as car.

This is possible in Scheme:

  (map car l)
but not in Common Lisp, since typically, the variable car would not have an ordinary value. The solution is a special form called function, but usually abbreviated #'. Here is how to write the same thing in Common Lisp (also taking into account the fact that the Scheme function map is called mapcar in Common Lisp):
  (mapcar #'car l)

Minor variations in syntax

There are some minor differences in syntax between Scheme and Common Lisp. To define a procedure in Scheme, you would use the define special form like this:
  (define (f l)
    (g (reverse l)))
whereas in Common Lisp, you would use the defun special form with slightly different syntax:
  (defun f (l)
    (g (reverse l)))
Other differences exist, like set! in Scheme and setq in Common Lisp, and map in Scheme which does not correspond to the Common Lisp function with the same name, but instead to the Common Lisp function mapcar