Introductory R7RS Scheme

Lesson 0

Scheme syntax is small, but it is different enough from Python, C, and JavaScript that it deserves a careful first pass.

Goal

This lesson teaches how to read the surface of Scheme programs: parentheses, prefix calls, values, identifiers, literals, quotation, simple sequencing, comments, and the conventions this course uses for examples.

You do not need to memorize the whole language here. The goal is to stop the syntax from being mysterious before Lesson 1 starts using it for definitions, tests, and small procedures.

Lesson 0 deliberately avoids the evaluation model in detail. The exact process by which Scheme evaluates calls, keeps track of names, and follows recursive calls belongs in later lessons. Recursion is mentioned here only as a preview, because it is central to Scheme and because you will see recursive examples very soon.

Course Conventions

Examples in this course are written as R7RS files when they are more than tiny fragments. That means they show their imports:

(import (scheme base)
        (scheme write)
        (scheme process-context))

Older Scheme courses often assume a particular REPL or implementation environment. This course tries to be explicit. If an example uses only R7RS small, it should say so. If it needs a SRFI or an implementation extension, it should say that before the code.

The current checked examples are run with CHICKEN 5.3.0 and the CHICKEN r7rs egg. A page is not called fully portable until it has been tested on more than one R7RS-capable implementation.

Expressions, Values, and Printed Results

A Scheme expression is a piece of code that can be evaluated to produce a value.

Some expressions are self-evaluating: their written form already is the value.

42
#t
"Scheme"

In an interactive Scheme session, typing one of these expressions usually prints the value back to you. The expression 42 evaluates to the number 42. The expression #t evaluates to true. The expression "Scheme" evaluates to a string.


A procedure call is also an expression. It asks Scheme to apply a procedure to arguments and produce a value.

(+ 2 3)

This means "call the procedure named + with the arguments 2 and 3." It evaluates to 5.


The word "output" can mean two different things. In this course, value means the result of evaluating an expression. Printed output means text written by procedures such as display, write, or newline. In an interpreter window you often see the value of the last expression, but printing is a separate action.

(begin
  (display "hello")
  (newline)
  42)

That expression prints hello, then has the value 42. The browser evaluator below shows both because that is useful for learning, but the two ideas are distinct.


The value of an intermediate expression can disappear if no later expression uses it. In the example above, display writes text as a side effect, but the value returned by (display "hello") is not the value of the whole begin. The final expression, 42, supplies the value of the whole sequence.

How to Read a Procedure Call

A Scheme procedure call has this shape:

(procedure argument argument ...)

The procedure comes first. The arguments follow. The whole call is wrapped in parentheses.

In Python, C, or JavaScript, you might expect arithmetic to look like this:

2 + 3

Scheme writes the operation in the same position for every call, including arithmetic:

(+ 2 3)
(* 4 5)
(> 10 7)
(string-append "r" "7" ".rs")

This regular shape matters. The same visual rule works for arithmetic, string procedures, list procedures, predicates, and user-defined procedures.

A useful reading habit is to name the first element and then list the arguments:

(* (+ 2 3)
   (- 10 6))

The outer procedure is *. Its two arguments are (+ 2 3) and (- 10 6). Those arguments are themselves procedure calls.

Parentheses Are Structure

In C and JavaScript, braces often mark statement blocks. In Python, indentation marks blocks. In Scheme, parentheses mark expression structure.

The indentation is only a reading convention; the parentheses define the structure.

(define (between? low value high)
  (and (<= low value)
       (<= value high)))

The usual Lisp indentation convention is to line up related subexpressions under the expression they belong to. Do not count closing parentheses by eye. Read inward from the opening parenthesis: what procedure or form starts here, and what are its parts?

Comments

A semicolon starts a comment that runs to the end of the line. Scheme ignores the comment text when it reads the program.

; This whole line is a comment.

(+ 2 3) ; This comment follows an expression.

(define radius 10) ; The name radius is now bound to 10.

Comments are for readers. They are useful for naming an intention, explaining a surprising choice, or marking a short exercise. They do not change the value of an expression.

Several Expressions and begin

Many interactive systems let you enter several expressions one after another. They are evaluated in order, and the value that gets reported at the end is usually the value of the last expression.


In a program, the standard form for saying "evaluate these expressions in order and use the last value" is begin.

(begin
  (+ 1 2) ; evaluated, then ignored
  (* 3 4)) ; final expression, so this is the value

The first expression evaluates to 3, but that value is not used by anything and is not the value of the whole begin. It is simply discarded. The whole begin evaluates to 12, the value of the last expression.

For now, read begin as sequencing. Later lessons will explain where sequencing matters, especially when a program performs output or uses mutation.

Identifiers Are More Flexible Than You May Expect

In Python, C, and JavaScript, +, ?, !, and -> are operators or punctuation. In Scheme, many names that look like punctuation to other languages are ordinary identifiers.

These are normal Scheme names:

+
<=
large?
set!
list->vector
two-plus-three

That does not mean every character can appear anywhere. Scheme still has lexical rules. But the everyday rule of thumb is useful: names are not limited to the identifier style of C-like languages.

A trailing ? conventionally marks a predicate, a procedure that asks a yes/no question:

(number? 10)
(null? '())
(large? 12)

A trailing ! conventionally warns that a procedure mutates something:

set!
vector-set!
string-fill!

The arrow -> often means conversion:

string->symbol
list->vector

These are conventions, not magic. The characters are part of the name.

Procedures Are Values Too

In Scheme, procedures are values. The expression + by itself is not an addition expression. It is a name lookup that evaluates to the addition procedure.

+

Different implementations print procedure values differently. One might print something like #<procedure:+>; another might use a different notation. The exact printed representation is not the point. The important point is that a procedure can be named, passed around, and called.


The expression above asks whether the value of + is a procedure.

Definitions and Names

A definition creates a binding: an association between an identifier and a value.

(define two-plus-three (+ 2 3))

two-plus-three

After the definition, the identifier two-plus-three evaluates to 5.


A procedure definition gives a name to a procedure.

(define (large? n)
  (> n 10))

(large? 12)

The name n is the formal parameter. It is the name used inside the procedure body. In the call (large? 12), the expression 12 is the actual argument expression, and its value is the actual argument value.

Do not worry yet about all the machinery behind procedure calls. Lesson 1 will use definitions heavily; later lessons will explain evaluation more carefully.

Quotation: Code as Data

Quotation is the one idea in this lesson that may feel genuinely unusual. It is central to Scheme, because Scheme code and Scheme data have closely related shapes.

The expression below is a procedure call:

(+ 2 3)

The expression below is data:

'(+ 2 3)

The leading quote prevents evaluation of the following datum. Instead of calling +, Scheme produces a list whose elements are the symbol + and the numbers 2 and 3.

The empty list is usually written with the same shorthand:

'()

This means "quote the empty list." A bare () is not a procedure call with a missing procedure; in portable Scheme, do not use it as an expression. Write '() when you mean the empty list as data. You will see '() often as the base case for list-processing procedures and as the value meaning "no elements here."

Non-empty quoted lists follow the same idea:

'(red green blue)

That produces a list of three symbols. Without the quote, (red green blue) would be read as a procedure call to a procedure named red with two arguments. Later lessons will explain list construction in detail, including cons, list, pairs, proper lists, and why '() is the natural endpoint of many list-building processes.

You can write quote in its full form:

(quote (+ 2 3))

The apostrophe is reader shorthand for the same idea:

'(+ 2 3)

Symbols are often introduced with quote:

'finish!
'list->vector
'empty-list

This is a common source of confusion:

list->vector
'list->vector

The first expression is a name lookup: find the value bound to the identifier list->vector. The second expression is data: produce the symbol whose name is list->vector.


The browser evaluator reports the value of the last expression, so this block displays the symbol. If you delete the quote on the second line, you ask for the procedure value instead.

Quasiquote uses a backquote:

`(hello ,name)

You do not need quasiquote yet. For now, read it as a more flexible kind of quotation that can include selected evaluated parts. It matters for macros and program-generating programs, so it will get its own later explanation.

Forms, Calls, and Special Cases

Most parenthesized expressions are procedure calls, but not all of them. Some are special forms. A special form has its own evaluation rule.

(define two-plus-three (+ 2 3))

(if (> two-plus-three 4)
    'large-enough
    'too-small)

(begin
  (display "done")
  (newline)
  two-plus-three)

define creates a binding. if chooses which branch to evaluate. begin evaluates expressions in order and returns the last value. They are not ordinary procedure calls, because their parts are not all evaluated the same way ordinary procedure arguments are.

This course will usually say "form" when the expression has a special evaluation rule, and "procedure call" when the first position is evaluated as a procedure.

Recursion, Only as a Preview

Recursion means that a procedure can call itself. Scheme programmers use recursion often because it matches the shape of many problems: a list is either empty or it has a first element and the rest of the list; a natural number is either zero or one step away from a smaller number.

You do not need to write recursive procedures in Lesson 0. The important reading skill is simpler: when you eventually see a procedure call itself, do not treat that as magic punctuation. It is still a procedure call, written with the same parenthesized shape as every other call.

A later lesson will explain how recursive calls make progress, how base cases stop the process, and how to test recursive procedures.

Highlighting Convention

Code blocks on r7.rs use static highlighting generated when the site is built.

The highlighting is a reading aid, not part of Scheme. Scheme itself does not know about these colours.

First Checked Example

The example source for this lesson is published at /examples/r7rs/intro/lesson-00/. It has a raw source link on that page.

(define two-plus-three (+ 2 3))

(define begin-result
  (begin
    (+ 1 2)
    (* 3 4)))

(define quoted-symbol (quote finish!))
(define quoted-list '(+ 2 3))

(define name 'Ada)
(define quasiquoted-list `(hello ,name))

(check-equal 'begin-last-value 12 begin-result)
(check-equal 'quote-word 'finish! quoted-symbol)
(check-equal 'quoted-list '(+ 2 3) quoted-list)
(check-equal 'quasiquote-preview '(hello Ada) quasiquoted-list)
(check-equal 'procedure-object #t (procedure? +))

This example is not interesting as an application program. It is useful because it demonstrates how Scheme names, values, sequencing, and quotation behave in checked R7RS code.

Basic Glossary

Exercises

Interactive Examples

Some early examples can be tried directly in the browser. The browser evaluator uses BiwaScheme 0.8.3, loaded from this site only when an interactive block is used. It is useful for expression-level practice, but it is not the course's conformance authority. The detailed compatibility note is at /learn/intro-r7rs/browser-evaluator/.


The full checked lesson files still run outside the browser with real Scheme implementations. BiwaScheme 0.8.3 documents several important differences from R7RS small: it lacks syntax-rules, exceptions, and the R7RS library system. That means it cannot run lesson files that begin with (import (scheme base) ...) unchanged.

Numbers need special care. R7RS distinguishes exact and inexact numbers, and the standard numeric tower matters in later lessons. BiwaScheme documents a JavaScript limitation: integers are not distinguished from floats. For basic arithmetic snippets, this is usually fine. For exactness, rational numbers, numeric tower behavior, or any lesson that depends on exact vs inexact results, the browser evaluator is only a convenience and the checked Scheme implementation is authoritative.

That choice is deliberate: the course should not teach behavior that differs from R7RS without warning.

Next

Lesson 1: expressions, names, and simple tests uses this syntax to define procedures and run simple checks. If (+ 2 3), (begin (+ 1 2) (* 3 4)), 'finish!, and (define two-plus-three (+ 2 3)) all make sense, you are ready to continue.