CS 331 Spring 2025 > A Quick Introduction to Scheme
CS 331 Spring 2025
A Quick Introduction to Scheme
By Glenn G. Chappell
Text updated: 2025-04-29
Table of Contents
- The Scheme Programming Language
- Expressions
- Defining
- Source Files
- Selection & Recursion
- Local Variables
- Lists & Quoting
- First-Class Code
- Topics Not Covered
- Copyright & License
1. The Scheme Programming Language
History
Scheme is a programming language created at the MIT Artificial Intelligence Lab around 1970 by Guy Steele and Gerald Sussman. It was based on the Lisp programming language, which was initiated in the late 1950s by MIT professor John McCarthy and Dartmouth student Steve Russell.
Scheme has found a niche in teaching computer programming from a theoretical point of view. Books like The Little Schemer by Daniel P. Friedman introduce various CS concepts using Scheme.
Scheme was standardized in 1991 by IEEE. A series of defacto standards have since been issued as reports by a group called the Scheme Language Steering Committee. The most recent came in 2013; it is called “R7RS” (Revised7 Report on the Algorithmic Language Scheme).
In 1994, the Rice University Programming Languages Team released a version of Scheme called PLT Scheme. Subsequently, this evolved away from standard Scheme; it is now known as Racket. Racket is distributed with a simple IDE called DrRacket, which supports Racket, standard Scheme, and other similar programming languages.
Characteristics
Scheme is a programming language in the Lisp family. It was designed according to a minimalist philosophy. As a result, Scheme is a rather smaller and simpler than some other programming languages in the Lisp family.
Like most other Lisp-family programming languages, Scheme code consists of parenthesized lists whose items are separated by space. These lists may contain other lists, or atoms, which are not lists.
[Scheme]
(define (hello-world) (begin (display "Hello, world!") (newline) ) )
Above, define
is a symbol.
So are
hello-world
,
begin
,
display
,
and
newline
.
However, "Hello, world!"
is not a symbol, but a string literal.
All of these are atoms.
Scheme is generally considered a functional programming language, but, unlike Haskell, Scheme is not pure: it allows for side effects and mutable data. However, quite a lot can be done with Scheme without making use of side effects. In this article I avoid side effects, except when doing I/O.
Scheme typing is dynamic and implicit. Scheme has a fixed set of types—something like 36 of them. New types cannot be created. By default, function parameters are checked via duck typing. So Scheme’s type system is similar to that of Lua, but a bit more complicated.
Scheme is extensible: we can define new flow-of-control contructs, as well as new ways of defining things. And ours have equal status with the standard ones.
Execution & Conventions Used in This Article
As with many modern programming languages,
Scheme can be used interactively,
and Scheme programs can be stored in source files.
The filenames of Scheme source files traditionally end with
the suffix “.scm
”.
Examples in this article assume the use of DrRacket. However, standard Scheme is covered, not the Racket programming language.
To make a file executable in DrRacket, its programming language must be specified at the beginning of the file, as follows.
[DrRacket Convention]
#lang scheme
Note that the above is not standard Scheme.
Source files can be loaded into DrRacket using a standard menu interface. The file can be editing and saved. Hitting the “Run” button at the top of the DrRacket window loads the file into Scheme and executes it. If execution hangs, hit the “Stop” button. If and when the code successfully completes execution, the bottom half of the DrRacket window becomes an interactive Scheme environment in which all global symbols defined in the source file are available for use.
In the interactive Scheme examples in this document,
boldface typewriter-font
characters
are those typed by the user.
Brownish non-bold typewriter-font
characters
show Scheme output.
As you read this introduction,
I recommend that you follow along with DrRacket.
2. Expressions
If an expression is typed in the Scheme interactive environment, then the expression is evaluated, and the result is printed. Thus, the environment is known as a Read-Eval-Print Loop (REPL).
[Interactive Scheme]
> 37 37 > -400 -400
However, using infix operators in the usual manner will not produce the expected results
[Interactive Scheme]
> 3 + 4 3 #<procedure:+> 4
A Scheme expression that consists of more than just an atom is always a list. The first item in the list is whatever operator or function is being applied. The rest of the items are its operands/arguments.
[Interactive Scheme]
> (+ 3 4) 7
Some operators can take more than two arguments.
[Interactive Scheme]
> (+ 1 2 3 4) 10
Operands can be other expressions.
[Interactive Scheme]
> (+ (* 5 7) 6 (* -1 3)) 38
Floating-point numbers, exact rational numbers, and complex numbers are also available.
[Interactive Scheme]
> (+ 3.2 4) 7.2 > (+ 1/3 1/2) 5/6 > (+ 7-2i 3+4i) 10+2i
Numeric comparison operators in Scheme are mostly
what we expect,
except that numeric equality is “=
”,
and there does not seem to be an inequality operator.
Boolean literals are #t
and #f
.
[Interactive Scheme]
> (>= 3 4) #f
Logical operators are and
, or
,
and not
.
[Interactive Scheme]
> (and (> 5 2) (not (= 3 4))) #t
For a list that forms an expression, the normal evaluation rule is as follows.
- Attempt to evaluate each list item.
- Attempt to call the result from the first item, as a procedure (Scheme-speak for function), with the results from the others as its arguments.
- Whatever the procedure returns is the value of the expression.
3. Defining
To bind a symbol to a value, use
define
.
This takes a symbol
and an expression.
The expression is evaluated,
and the symbol is bound to the result.
The symbol can then be used in expressions.
[Interactive Scheme]
> (define x 3) > x 3 > (+ x 10) 13 > (define abc (+ x 100)) > abc 103
We can also use define
to create
a procedure.
Once again, define
takes two arguments.
The first is a picture
of a call to our procedure,
which may have arguments.
The second is an expression.
However, the expression is not evaluated yet.
When the procedure we create is later called,
the parameters, if any, are given their values,
and then the expression is evaluated.
[Interactive Scheme]
> (define (square x) (* x x))
Above, “(square x)
”
is what I am calling a picture of a procedure call.
square
is the name of the procedure,
and x
is its parameter.
We call a defined procedure as usual.
[Interactive Scheme]
> (square 5) 25
4. Source Files
Again, the standard filename suffix for a
Scheme source file is
“.scm
”.
A Scheme source file will consists mostly of definitions of symbols.
[Scheme]
#lang scheme ; squarecube.scm ; square ; Returns the square of its argument. (define (square x) (* x x)) ; cube ; Returns the cube of its argument. (define (cube x) (* x x x))
A single-line comment in Scheme begins with semicolon (;
)
and goes to end-of-line.
Multiline comments begin with “#|
”
and end with “|#
”
Interestingly, Scheme has a third kind of comment.
To comment out a single expression,
use “#;
”
followed by the expression.
This kind of comment does not treat line breaks in any special way.
[Scheme]
#; (this is commented out) (this is not commented out)
As stated above, once code is entered in the editor, it can be executed by clicking the “Run” button at the top of the DrRacket window.
5. Selection & Recursion
if
takes three expressions.
The first expression is evaluated.
If the result is anything
other than #f
(for example, #t
),
then the second expression is evaluated,
and the result is returned.
If the result is #f
,
then the third expression is evaluated
and the result is returned.
Typically, the first expression evaluates to a boolean,
but in case it does not,
note that anything other than #f
counts as truthy.
[Interactive Scheme]
> (if (= 3 3) 42 6) 42
To make a recursive call in Scheme, a procedure simply calls itself. Putting all this together, here is a recursive factorial.
[Scheme]
(define (fact n) (if (= n 0) 1 (* n (fact (- n 1))) ) )
[Interactive Scheme]
> (fact 5) 120
6. Local Variables
Local variables can be defined in Scheme
using a let
construction,
which is similar to the let
construction in Haskell.
Here is Haskell code for a recursive factorial function
that uses a local varible.
First we do it with where
.
[Haskell]
fact' n = if n == 0 then 1 else n * fn1 where fn1 = fact' (n-1)
Next we do it with let
.
[Haskell]
fact'' n = if n == 0 then 1 else let fn1 = fact'' (n-1) in n * fn1
Lastly, here is much the same thing in Scheme.
[Scheme]
(define (fact2 n) (if (= n 0) 1 (let ( (fn1 (fact2 (- n 1))) ) (* n fn1) ) ) )
In Scheme, let
takes two arguments.
The first is a list of 2-item lists.
Each of the 2-item lists holds a symbol and an expression.
The expression is evaluated and the symbol is bound to the result.
The second argument of let is an expression.
This expression is evaluated with each of the symbols bound
as indicated.
The result is returned.
So, above, if n
is not zero,
the fn1
is bound to
the value of (fact2 (- n 1))
,
the expression (* n fn1)
is evaluated,
and the result is returned.
With so many parentheses,
it can be difficult to see where they match up.
For this reason, Scheme allows
brackets to be used as well.
But a bracket cannot match with a parenthesis.
So “[(abc)]
” is good,
but “([abc)]
” is not.
It is traditional to put each of the 2-item lists in a let in brackets. Here is the above code rewritten accordingly.
[Scheme]
(define (fact2 n) (if (= n 0) 1 (let ( [fn1 (fact2 (- n 1))] ) (* n fn1) ) ) )
7. Lists & Quoting
The syntax we have been using to represent code
can also represent data.
For example,
the procedure list
takes an arbitrary number of expressions.
The expressions are evaluated,
and a list of all the results is returned.
[Interactive Scheme]
> (list (+ 1 2) (* 3 4) 5) (3 12 5)
Simply entering a list—e.g., (3 12 5)
—at
the scheme prompt is a problem,
since things we enter are evaluated,
and 3
is not a procedure.
We can deal with this using the procedure list
.
Another way to deal with it is to precede the list
with a single quote ('
),
which supresses evaluation.
[Interactive Scheme]
> (3 12 5) application: not a procedure ... > (list 3 12 5) (3 12 5) > '(3 12 5) (3 12 5)
What follows the single quote is said to be quoted.
Note that list
evaluates its arguments,
while quoting suppresses all evaluation.
[Interactive Scheme]
> (list (+ 1 2) 5) (3 5) > '((+ 1 2) 5) ((+ 1 2) 5)
Lists are first-class. We can set a variable equal to a list.
[Interactive Scheme]
> (define t1 (list (+ 1 2) 5)) > t1 (3 5) > (define t2 '((+ 1 2) 5)) > t2 ((+ 1 2) 5)
Scheme has a number of procedures that allow us to construct lists and taken them apart.
car
takes a nonempty list and returns its first item.
[Interactive Scheme]
> (car '(11 22 33 44 55)) 11
Similarly, cdr
takes a nonempty list and returns
a list containing all items except the first.
[Interactive Scheme]
> (cdr '(11 22 33 44 55)) (22 33 44 55)
We can do fancier things by putting
car
and cdr
together.
For example, for a list with at least 2 items,
the car
of the cdr
will be the second item.
[Interactive Scheme]
> (car (cdr '(11 22 33 44 55))) 22
By the way, the odd names
car
and cdr
come from an implementation detail
of an early version of the Lisp programming language.
They originally stood for
“Contents of the Address Register”
and
“Contents of the Data Register”,
respectively.
cons
takes an item and a list.
It returns a list with the item prepended to the given list—like
the colon operator in Haskell.
[Interactive Scheme]
> (cons 11 '(22 33 44 55)) (11 22 33 44 55) > (cons 10 (cons 20 (cons 30 '()))) (10 20 30)
8. First-Class Code
As you might have guessed, Scheme has first-class functions (which it calls procedures). But Scheme goes further than that; it has first-class code. The great strength of Scheme—and other Lisp-family programming languages—is that code can be stored in variables, passed around, modified at runtime, and executed.
To evaluate a list as an expression,
pass it to eval
.
[Interactive Scheme]
> (define w '(+ 5 (* 2 3))) > w (+ 5 (* 2 3)) > (eval w) 11
Here is a procedure that takes a list,
adds “+
” on the beginning,
evaluates, and returns the retult.
[Scheme]
(define (add-plus-and-eval xs) (let ( [pxs (cons '+ xs)] ) (eval pxs) ) )
Now let’s apply the above to the list
((* 2 3) (* 3 4))
.
This will construct the list
(+ (* 2 3) (* 3 4))
which evaluates to \((2 \times 3) + (3 \times 4) = 18\).
[Interactive Scheme]
> (define y '((* 2 3) (* 3 4))) > y ((* 2 3) (* 3 4)) > (add-plus-and-eval y) 18
Manipulating code at runtime in Scheme is not nearly as cumbersome as the above might suggest. Scheme allows for what are called macros. These form a convenient way to specify code transformations to be performed at runtime.
9. Topics Not Covered
This has only been a brief introduction. There is more to say about Scheme. Below are a few topics that were not covered here.
- Other Selection Constructs
- Iteration Constructs
- Type Checking
- Pairs & Data Representation
- Character & String Handling
- Modifying Variables
- Procedures with a Variable Number of Arguments
- Data Structures
- Quasiquoting
- I/O
- Error Handling
- Macros
- Continuations
10. Copyright & License
© 2021–2025 Glenn G. Chappell
A Quick Introduction to Scheme
is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.