The Read-Eval-Print Loop

Racket is Interactive

Racket is an interactive language. Rather than writing a large program as a whole and then translating it "in batch", in Racket we write a little piece of program, evaluate it, and then do the same with another piece. Quite different from batch-style programming, this allows quick turnaround in software development — which accounts for the prevalent use of interactive languages in (rapid) prototyping.

Do you use any interactive languages? Most of you use at least one: Python. Some of you know others, including Perl, Ruby, and bash or PowerShell.

An interactive language can be either interpreted or compiled. So can batch languages, but there usually isn't much point in interpreting them. Dr. Racket both interprets and compiles: It interprets expressions but compiles procedure definitions. The result is faster response time than pure interpretation. Generally, though, I will refer to "the Racket interpreter".

Perl and Java are interpreted, but they aren't generally used in an interactive fashion. Racket's uncle Common Lisp is another interactive language that uses both interpretation or compilation.

What does the Racket interpreter do with the Racket expressions that it reads? It evaluates them! The Racket interpreter works in much the same way that any interactive language interpreter works:

  1. It reads an expression.
  2. It evaluates the expression.
  3. It prints the value of the expression.

The "top-level" behavior of the interpreter is to cycle through this sequence of actions repeatedly. In Racket this is implemented recursively. A simplified version of the process might look like:

(define run
  (lambda ()
    (print (eval (read)))
    (run)))

This cycle is called the read-eval-print loop. This behavior is the foundation for all that we will be doing with language interpretation in this course. Be sure to understand it!

Reading Expressions

The technical term for the read step is parsing, which translates the string of characters that the user enters into a data structure on which the evaluator can operate. During this semester, we will discuss how syntax is described, and we will use Racket's parser for our own interpreters, but we will not dig into the mechanics of parsing. They are properly a topic of the compiler course, CS 4550 Translation of Programming Languages -- a course I strongly recommend!

Evaluating Expressions

The evaluation of expressions is the main focus of this course. The basic mechanism for evaluation is one with which you are probably already familiar, if only by example in other languages: To evaluate the expression (operator operand1 operand2 ...),

  1. Evaluate each of the subexpressions, including the operator.
  2. Apply the leftmost result (which, in most of our code, will be a function) to the operands.

Because some operators are special forms, we know that a Racket interpreter must evaluate the operator first and the operands next. Otherwise, it wouldn't know whether to use the standard procedure application rule or the special form's rule.

eval is recursive. Evaluating the subexpressions means making a recursive call to eval for each expression. If one of the subexpressions is a compound expression, then it, too, is evaluated in this same way. Simplicity can give rise to power...

This explanation is a bit oversimplified, because it omits answers to some potentially important questions. For example, in what order are the subexpressions evaluated? But this general algorithm will serve as a workhorse for us this semester.

Quick Exercise: Can you think of a situation in which the order of evaluation matters? Think of a scenario in another programming language, such as Java. (Try x + ++x.)

Printing Expressions

Printing the result of evaluating an expression -- which is itself an expression! -- is the inverse of reading one. The printer must translate the internal representation of the expression into a string of characters that can be written to the terminal. Again, we will largely rely on the Racket interpreter's printing mechanism this semester.

The exact interface to any Racket interpreter is implementation-specific. But the basic read (an expression) - eval(uate the expression) - print (the result) loop is a constant.