Frequently Asked Questions
Why do you talk about Racket and Scheme?
Racket is a fork of Scheme, the simple language at the core of this course for many years. Scheme was created primarily as an experiment in understanding how programming languages work. Racket retains its basic favor, but it also adds many, many features that make the language more relevant in the 2010s. (It also changes a few details that make it more useful to modern programmers.)
We use Racket in this course because it is more modern, has better tools, and a thriving community doing cool work. But there are so many valuable references from the Scheme world that it's hard to talk about Racket in this course without referring to Scheme.
Scheme modernized many things about Lisp, but kept these names for reasons both of culture and convenience. You can read more about them, including the convenience reasons and even the IBM 704 macro code for the car and cdr macros, at the Wikipedia pages for car and cdr.
Racket modernizes Scheme in many ways, including by adopting Common Lisp's names for these fundamental procedures: first and rest.
cons is the sort of abbreviation that most programmers love. It is the procedure for constructing lists. More specifically, cons allocates a single two-address cell, which we usually just call a cons cell. In this sense, cons is an inverse of car and cdr.
To determine if a procedure is a Scheme primitive, look into the Scheme language definition under the appropriate category. For example, if you would like to know if add1 is a Scheme primitive, go to the section of the language definition on numbers to see if it is defined. (It's not.) Or if you would like to know if rember is a Racket primitive, go to the section of the language definition on lists to see if it is defined. (It's not.)
You have another way to determine if a procedure is built into Racket: evaluate the name of the procedure in Dr. Racket. If you have not defined a procedure with that name, and the name evaluates to a compiled #<procedure:...>, then the procedure must be a Racket primitive.
> first #<procedure:first>If it causes an error because the name is not bound to a value, then it's not.
> foo ... foo: undefined; cannot reference an identifier before its definition
Programming language researchers have long been working on module systems that allow programmers to share libraries of procedures more easily. Dr. Racket provides a well-developed, full-featured module system.
Racket extends Scheme in radical ways, but modularizes extensions in separate languages. We will talk a bit about this later in the semester.
Does that mean we cannot write programs using these ideas? No. Scheme is more than powerful enough to implement all of these ideas, plus ideas more complex than you are used to using. It simply means that they we will have to find an implementation of the idea, or roll our own. For example, I know of a couple of canonical implementations of object-oriented programming primitives for Scheme. If I want to do full OOP right now, I would use one of them. Of course, as you'll see later in the semester, we can implement OOP concepts on our own in relatively little code.
So, people write their own and share.
Where is the weakness in that? Portability is sometimes a problem. Unless a programmer implements his library using only standard Scheme, there is a risk that you won't be able to use his library on your system. It may sound easy to use only standard Scheme, but in the heat of implementing a big package it is awfully tempting sometimes to use some of the non-standard features available in your environment. (Dr. Racket is a siren of this sort.)
What else? Scheme is different than most other languages programmers use. It looks different, with all those parentheses and lambdas. It feels different, with its emphasis on functional programming and no side effects. Unfamiliarity can be a weakness, because it makes the language less immediately accessible to new programmers. It takes more effort to learn, because it changes the programmer's habits.
The use of function composition and higher-order procedures (we'll talk about that soon!) can result in "dense" code -- a small amount of code that implements a lot of different ideas. Unlike Java and Ada, there isn't much dead space or many dead characters. When a programmer is used to roomier code, Scheme code can intimidate them.
The good news is that these weaknesses can be overcome by time and effort. The unfamiliar can become familar. And on the other side is a productivity and a sense of power that is hard to imagine when slogging away in Java, C, or Ada.
The lambda calculus is a formal mathematical system devised by Alonzo Church to investigate functions, function application, and recursion. It has influenced many programming languages but none more so than the functional programming languages. Lisp was the first of these although only the "pure" Lisp sublanguage can be called a true functional language. Haskell, Miranda, and ML are more recent examples. Lambda calculus also provides the meta-language for formal definitions in denotational semantics. It has a good claim to be the prototype programming language.
The lambda calculus is in many ways the prototype for all programming languages. Like the Turing machine, it is capable of implementing any computable function. Unlike the Turing machine, it focuses on the transformation rules that implement a function, rather than the underlying machine implementation. Hence it lies closer to the level of software than the Turing machine.
You can read a lot more about the lambda calculus at the Monash University site. You shouldn't be surprised later in the course when we use and interpret a subset of Scheme that is remarkably similar to Church's calculus.
The original Scheme ... was a Swiss army knife. Hewing close to the spirit of Alonzo Church's lambda calculus, it had just one of anything if it had one at all.
Because Scheme is minimal whereas many of the languages you know well are decidely not (C++, Ada, even Java), it gives us a way to talk about the trade-offs between small and large languages. Because Scheme is minimal, it leaves us a lot of freedom to talk about most every language feature, what they are useful for, how to implement them, and what trade-offs they impose on us. Because Scheme is minimal, it is small enough for us to master it well enough to implement language interpreters in it.
One of the reasons that Racket works so well for studying languages is that symbols and lists are two of its primary data types. In any lanhguage, once you move past the surface syntax, a program can be thought of as a tree of symbols and values. Trees are easily represented as lists of lists in Racket, and symbols are first-order values.
Finally, as we will learn soon, programs are recursively defined data structures, and using a functional programming style exposes this in the interpreters we write. Even more, functional programming takes advantage of the recursive nature of programs to produce small, simple, and relatively easy to understand interpreters.
This is true in a theoretical sense, because Racket -- and even Scheme -- is "universal": any computable function can be computed in it.
But Racket is universal in a practical sense, too. People use Racket to write business data processing software, network control software, AI programs, and even computer games.
The Racket team eats its own dog food: Racket and all of its tools, including Dr. Racket, are written in Racket even the web server that supports http://racket-lang.org/.
Of course, like any language, it is better suited for some tasks than for others. For example, Scheme may not be your first choice for "bit twiddling" sorts of programs, because most of its primitives are rather high-level and it does not connect you directly to any particular machine architecture. Of course, the beauty of Scheme is that you can extend it to include any feature that you want but that basic Scheme does not have -- or you can rather easily write an interpreter for a more suitable task-specific language. That is The Racket Way.
But. If your question is meant to imply that use in "real" applications is necessary before you consider the language worthy of the effort to learn, then my (only partly) tongue-in-cheek answer becomes, "I don't care". Not every language or skill you learn is directly applicable to your job tomorrow. Our goal at the university is for you to learn ideas that last for a while. No one can predict with any certainty what language -- or even kind of language -- you will be using in two, five, or ten, years. But I can tell you what principles will underlie their design, implementation, and use.
Learning Racket will help you to learn those principles. It will also help you to learn functional languages, and languages with functional features, more easily later. Practice learning a new language is worth the effort if only because it makes learning the next one easier! (Of course, if none of the tools and languages that you learn as an undergraduate are used in the "real world", then you may have cause to complain...)
Then again, if you believe Paul Graham, you should be using Racket or some other Lisp precisely because it is the most powerful language around.
Racket forked Scheme. Scheme forked Lisp. Lisp dates to 1958. The first compiler was written in 1962. Scheme dates to the fall of 1975. The code and tool base of Racket date to the mid-1990s. Lisp evolves and survives.