Racket Frequently Asked Questions
This is your one-stop shop for questions that people often have when they first encounter Racket.
What is Functional Programming?
Functional Programming is a programming paradigm, or way of thinking about language structure, which prioritizes functions as the most important part of the language. Everything in the language is achieved by calling functions, which call other functions, which call even more functions.
Some of the differences, in practice, from the imperative programming languages you may be used to, are as follows:
-
There are no "statements". Everything is an expression, which is to say that it can be evaluated. In languages like Java or C, certain elements like a variable assignment
int x = 2;or a function with avoidreturn type cannot be evaluated to anything. These concepts don't exist in functional programming. Everything returns a value and that return value is the entire purpose of the expression. Expressions which have side effects other than returning a value, (e.g. assignments, definitions, printing to console, drawing to screen) are kept to an absolute minimum. -
This means that there is also no such thing as variables, at least in a traditional sense. Values are kept in the program's memory solely by passing them from expression to expression as parameters or return values. Most functional languages do have a feature called an environment, which allows the storage of some values, but generally you cannot change a value once it has been added to an environment. (In other words, environments values are immutable.) The overall goal is that in a functional language, you should never be able to call the same procedure with the same arguments twice and get two different results.
Racket is not a purely functional language, and so it has functions like
set!which allow you to modify environment values. By convention, all such functions which have the side effect of mutating (changing) some stored data have a "!" suffix in their name. -
Functions are a data type. They can be passed to one another as parameters and as return values, just like any other data type like integers or strings or booleans. Other languages often have some limited implementation of this, often referred to as lambda expressions, but this concept takes center stage in functional programming. You will routinely encounter functions which produce other functions, functions which perform operations on functions, functions which apply functions to other functions, and many more bizarre configurations, so it's best to get used to this idea soon.
Finally, an example to illustrate the difference between imperative and functional programming, written in Javascript (which is not Racket, but it's easy to read and bridges the two paradigms nicely):
// Imperative
var array = [1,2,3,4,5,6,7,8,9,10];
var result = 0;
for(let i = 0; i < array.length; i++) {
array[i] = array[i] * array[i];
result += array[i];
}
console.log(result);
// Functional
console.log([1,2,3,4,5,6,7,8,9,10].map(n => n * n).reduce((sum,n) => sum + n));
What is Racket?
Racket is a functional programming language which is a descendant of a language called Lisp. Its website lists its main use cases as web applications, math/statistics, and databases. One big feature that sets Racket apart from other related languages is its language module feature, (the little #lang racket that you see at the top of every program) which allows you to specify which flavor of the language you're using. Racket is really a collection of closely related languages all built from the same base language.
No actually, why are we talking about Racket? My class uses Scheme/Clojure/etc.
Scheme, Clojure, Racket, and a few others are all descendant languages of Lisp, which is the second-oldest high-level language still in popular use today (the first being FORTRAN). Clojure is the newest, created in 2007, while Racket came in 1995, Scheme in 1975 and Lisp back in 1958. They are all similar enough that the distinction won't matter for the purposes of this guide, however they do have some differences in available features. Lisp has a lot of different dialects/descendants that have sprung up over the years because the language is structured in a way that makes it easy to extend, i.e. create new language features using the original language.
Note that although these languages all share Lisp as an original ancestor, they are not necessarily descendants of each other. Racket is largely based off of Scheme, but Clojure is its own thing.
How should I format my Racket Code?
Take a look at the style guide for precise guidelines, but generally just format your code in a way that makes it most readable. Try to avoid putting too much code on one line, and line pieces up vertically when they are logically related, such as multiple clauses of a cond expression. DrRacket will do most of the indenting work for you, (you can re-indent your whole file any time using Ctrl + i) so you just have to worry about where to put line breaks.
Standard conventions suggest that you use kebab-case whenver naming things in Racket. Functions which return a boolean value should have a ? at the end of their name, while functions which make changes to existing environment data should have a ! at the end of their name.
What is a procedure?
The terms "procedure" and "function" are essentially interchangeable; they refer to the same thing except that the term "function" is typically used to describe a procedure which has been bound to an identifier in the environment.
What is an environment?
An environment is a data structure which holds all the associations between names and values that your program is aware of at any given point. Everything that you can refer to by name, including functions and parameters all must be "bound" to that name first by registering a name/value pair in the environment. We typically refer to the names as identifiers.
'((x 10) (y 4) (+ #<procedure>) (- #<procedure>))
Normally, there are only two times when binding occurs. When you specify the language module at the top of your program (#lang) that binds all the functions and other syntactic elements of the chosen language to their respective names so your program can use them. Secondly, whenever you call a procedure, the names of each of the parameters it takes are bound to the values that you pass in, which is what allows you to use those values in the procedure.
This brings up another important concept which is that environments are scoped. They live a seperate existence inside every call to a procedure and so any change to an environment only affects the environment in that current scope and lower. So, when parameters are bound to their passed-in values at the start of calling a procedure, those bindings do not exist outside of that procedure's scope.
Besides automatic binding, there are a limited number of special functions included in Racket that allow you to manually bind identifiers to values, such as define and let.
What is that period in my list?
(println (cons 'a 'b))
> '(a . b)
That strange notation is referred to as a pair. Pairs are the basic unit of pretty much all data structures in Racket. They consist of a first value and a second value, both of which can be any data type, including more pairs.
You may have accidentally created a pair when you were trying to create a list using the cons function. Lists in Racket are represented using the same linked list structure you may be familiar with from other classes. A list consists of a chain of pairs, each one holding a value in their first slot and a reference to the next pair in the chain in their second slot. The last pair in the chain holds a value of null in its second slot.
; A list:
'(a b c d e f g)
; How it is actually represented:
'(a . (b . (c . (d . (e . (f . (g . null)))))))
'() is actually equivalent to null.Most of the time, you'll just be working with lists, so if you see a period somewhere you've probably misused cons. If you're ever unsure, just remember that with cons, the first parameter should always be a single element and the second parameter should always be a list.
(cons 'a '(b c)) -> '(a . (b c)) -> '(a b c)
(cons '(a b) 'c) -> '((a b) . c)
Why do I have to put an apostrophe before lists and symbols?
That notation is referred to as quoting in Racket, and its purpose is to signal to the executor that what follows is to be interpreted as a datum (singular form of data) as opposed to some other structure. A datum can be a symbol, a boolean, a number, a string, a character, a keyword, a pair, a list, or any other data type.
Quoting is necessary when specifying symbol list or pair values because they would be interpreted as identifiers or expressions otherwise.
(define x 3)
(println x)
> 3
(println 'x)
> 'x
(println (/ 12 x))
> 4
(println '(/ 12 x))
> '(/ 12 x)
; You do NOT have to quote anything that is enclosed in a larger
; list/pair because they are already being interpreted as data
(println '(a (b c)))
> '(a (b c))
quote function. You can read about this topic in more detail here.
Quasiquoting and Unquoting
Ever looked at a quoted list and said to yourself, "Wow, I wish I could write code in this list!" Well, first of all, that's a pretty weird wish, and second of all, you can! By using a backtick ` instead of an apostrophe, you can create a quasiquote. This is a weaker form of quoting, which can be undone within the list using a comma, AKA an unquote. This allows you to specify a structure that overall is evaluated as a list, but has pieces within it which are meant to be interpreted as code. Here are some examples to illustrate:
`(1 2 3 ,(+ 2 2)) -> '(1 2 3 4)
(let ([x 'c]) `(a b ,x d)) -> '(a b c d)
; Nesting quasiquotes within quasiquotes?!
(let ([x 2])
`(`(1 2 3 ,(+ ,x ,x))
evaluates
to
(1 2 3,(+ x x))))
-> '(`(1 2 3 ,(+ 2 2)) evaluates to (1 2 3 4))
There is also one final quoting operation at your disposal, which is called unquote-splicing. This is the same as regular unquoting, except that the contained expression must evaluate to a list, and the contents of that list will be spliced into the surrounding structure, as the name implies. The shorthand for this operation is ,@.
; without splicing
`(1 2 3 ,(list 4 5 6 7) 8 9 10) -> '(1 2 3 (4 5 6 7) 8 9 10)
; with splicing
`(1 2 3 ,@(list 4 5 6 7) 8 9 10) -> '(1 2 3 4 5 6 7 8 9 10)
Quasiquoting is one of those features that is not used very often, mostly because there are more readable and computationally efficient ways to accomplish the same thing with normal code, but it is helpful in a pinch if you're trying to assemble data into a particular structure and don't want to deal with writing an ocean of cons, append and list calls, or if you feel like flexing on the non-quasiquoting normies.
What is a closure?
A closure is one of the more elusive concepts that you'll encounter in functional programming languages. It describes a function which makes use of identifiers defined outside of its own scope.
How does that even happen? Well, take a look at the following example:
; This defines a function called add-prefix, which
; takes a prefix string and returns a procedure that
; appends that prefix to a given name.
(define
(add-prefix prefix)
(lambda (name) (string-append prefix " " name)))
; Using add-prefix to define a new function called add-dr
(define add-dr (add-prefix "Dr."))
; using add-dr to add the prefix to a name
(add-dr "Racket") -> "Dr. Racket"
Did you notice something about the lambda expression up above? That's right, it uses the identifier prefix, even though that identifier is not defined within the lambda expression; it's defined right before as a parameter to add-prefix. In other programming languages, this would be impossible! (At least how it's depicted here. You could achieve a similar situation with classes in an object-oriented language.) However, because we can define new procedures/functions smack dab in the middle of an expression, it makes sense that they should be able to use identifiers from their surrounding context. That lambda expression is what we refer to as a closure.
We care about closures because they vastly increase what we are able to do with procedures. They allow procedures to have a sort of memory of their surrounding environment, even when all direct references to that environment have long since been thrown away. After the second expression up above, the program no longer remembers what value we passed into add-prefix. In fact, there is no way to access that value directly any longer. The only way that value can still be accessed is through add-dr, because it is a closure.
Closures have some really wacky capabilities if you start to dabble with them in conjunction with the less purely-functional side of Racket. Since they allow a procedure to store persistent data between calls, they can be made to behave in ways that you normally wouldn't associate with procedures/functions, such as this:
; A closure whose return value starts at 1 and
; increments by 1 each time it is called
(define counter
(let [(x 0)] ; Creates a local binding of x outside of the lambda's scope
(lambda ()
(set! x (add1 x)) ; Sets the new value of x to x+1
x)))
(counter) -> 1
(counter) -> 2
(counter) -> 3
Or, this...
(define self-delete
(lambda ()
(set! self-delete null)
(println "hi!")))
(self-delete)
> hi!
(self-delete)
> error: not a procedure
What is a symbol and how is it different from a string?
Symbols are a data type in Racket that you've probably never encountered in any other language. They are very similar to strings, in that they represent a string of characters, but while strings are denoted by surrounding them with double quotes, symbols have no quotes. (A single quote out front is still necessary in most cases to reduce ambiguity; see quoting for details.)
; This is a string
"Hello World!"
; This is a symbol
'Hello-World!
Besides the fact that symbols cannot contain whitespace characters, there are two main differences that set these data types apart.
The first is how identical values behave. Symbols, (when parsed normally,) are guaranteed to be interned, which means that if you write two symbols in different places in your code and they have the same characters in the same order, they are the same symbol, and will be computed as equivalent. (There are methods of generating uninterned symbols if necessary.) Strings, however, are not. Each string lives its own existence, and so even if two strings hold exactly the same information, they may evaluate as being not equal if they don't reference the same internal object.
The second is their usage. While strings are used to handle and manipulate display text, like in most applications, symbols are typically used to handle specifically the names and identifiers of things within the language. For example, the mode and exists flags for the open-output-file function, which might be specified with an enum object in other languages, are specified with symbols. Also, symbols are used to represent identifiers of values and functions when parsing code from data.
(eval '(+ 2 3)) -> 5
eval to represent the + function.