Share via


{ End Bracket }

Scheme Is Love

Don Box

For the past few years, it has become fashionable to embrace a dynamic language such as Perl, PHP, Python, or Ruby. While I'll admit to having a short but pleasurable tryst with Ruby, I believe I have found true love in the dialect of Lisp called Scheme.

In writing Scheme programs, I've learned a lot about coding, design, architecture, and aesthetics. On this front, Eric S. Raymond got it right in his essay, "How to Become a Hacker":

Lisp is worth learning for a different reason—the profound enlightenment experience you will have when you finally get it. That experience will make you a better programmer for the rest of your days, even if you never actually use Lisp itself a lot.

Everyone gets something different from their travels with Lisp. Here's what I saw along the way.

Small is beautiful. Scheme is a great example of a micro-kernel design style. Scheme defines a very small number of base mechanisms as part of the core language/runtime specification. For example, Scheme defines only one conditional in its base: the if special form. The if form works like the ? : operator from C/C++/C#:

(if abc "yes" "no")

This is equivalent to the following C expression:

abc ? "yes" : "no"

But Scheme and the C languages depart at this point. C defines a number of logical operators (&&, ||, !) and control flow statements (if, if/else, while, do/while) as language primitives. In contrast, Scheme defines all of these constructs as either procedures or macros. Does Scheme have the moral equivalent of C's basic conditionals and control constructs? Absolutely. What's interesting about Scheme is that these are all defined purely in terms of Scheme.

Lambda is more powerful than you think. Scheme programs are defined largely in terms of the lambda special form. Scheme lambdas are used to define functions. What's noteworthy about lambda is that functions are values just like integers are values. For example, the following Scheme code

(define s (lambda (y) (+ y 1)))

defines a new variable named s whose value is the function that calculates the successor of its argument. Here's the equivalent C#:

Converter<int,int> s = delegate(int y){ return y + 1; };

In both C# and Scheme, lambdas form lexical closures that capture the in-scope variables that are used by the newly defined function.Consider this C# code:

Converter<int,int> MakeAccumulator() {
  int total = 0;
  return delegate (int y) { return total += y; };
}

What I love about this approach versus defining a class is that I didn't need to go through the standard 20 questions one typically asks themselves when writing a new class. No class versus struct, interface versus abstract base, or other analysis was necessary. Instead I wrote a program that did what I wanted it to do. Period.

The difference between code and data is highly overrated. One thing all Lisp dialects have in common is that they build on a very simple mechanism (lists) that are used to build composite data structures. For example, the following Scheme expression

(list 1 2 3 4 5)

results in a five-element list containing the integers 1 through 5. In this case, list is a variable that is bound to a lambda that constructs a list out of its arguments.

In Lisp dialects, expressions themselves can be viewed as lists. Consider this simple Scheme expression:

(+ 1 2 3 4 5)

When evaluated, this expression results in the number 15. To turn off evaluation and keep this expression as plain, uninterpreted data, you can use the quote special form like this

(quote (+ 1 2 3 4 5))

which is longhand for:

'(+ 1 2 3 4 5)

Both of these expressions result in a six-element list, which begins with the symbol + followed by the integers 1 through 5. The ability to treat code as data allows me to apply general query and data manipulation techniques to my programs. To go back to the world of interpretation, I can ask the system to evaluate a data structure as if it is a legal Scheme expression like this:

(eval '(+ 1 2 3 4 5) (scheme-report-environment 5))

This melding of code and data is central to all dialects of Lisp, and is fundamental to the way Microsoft is integrating multiple expression languages (most notably SQL) in future versions of the Microsoft® .NET Framework.

Don Box is an architect on the Indigo project at Microsoft, working on next-generation Web service protocols and plumbing. His latest book is Essential .NET Volume 1: The Common Language Runtime (Addison-Wesley, 2002).