Glossary of Racket concepts
1 Introduction
2 Entries
Arity
Assignment
Binding
Boolean
Box
Byte string
Call
Channel
Chaperone
Character
Class
Closure
Collection
Combinator
Comprehension
Cons cell
Continuation
Contract
Core form
Currying
Custodian
Debugging
Definition
Display
Dr  Racket
DSL (domain-specific language)
Environment
Equality
Exact number
Executor
Exception
Expression (always rvalue? may result in one or more values)
Field
Fixnum
Flat contract
Flonum
Fold
Form
Formatting
Function
Functional programming (FP)
Functional update
Future
Generator
Generic API
GUI programming
Hash
Higher-order function
Hygiene
Identifier (differences to identifiers in other languages)
Identity (also refer to ‘eq?‘)
Impersonator
Inexact number → Exact number
Inspector
Interface (API)
Interface (OOP)
Keyword arguments (positional and keyword args are separate)
Lambda
Lang (as in ‘#lang‘)
Language-oriented programming
Let
Let over lambda
List
Location
Macro
Match
Match transformer
Method
Module
Named let
Namespace
Naming conventions
Number
Numeric tower
Opaque
Package
Pair
Parameter
Partial application and currying
Pattern (in regular expressions)
Pattern (in macro definitions)
Phase
Place
Polymorphism (rarely used, compare with other languages; see also generic code)
Port
Predicate
Print
Procedure
Profiling
Prompt
Provide
Quasiquote
Quote
Rn  RS (as in R5RS, R6RS etc.)
Raco
Reader (for parsing code)
Record
Require
Rule (in macros; probably other uses, which ones?)
Safe operation
Scheme
Scribble
Sequence
Set
Shadowing
Splicing
SRFI
Standard library
Stream
String, character, byte string
Struct
Symbol
Syntactic form
Syntax (different meanings)
Syntax transformer
Tail call
Tail position
Thread
Thunk
Transparent
Trust level
Trusted code
Typed Racket
Undefined (why do we need this if we have ‘void‘?)
Unit
Unsafe operation
Untrusted code
Value (sometimes "object", but may be confused with OOP concept)
Values (multiple values, as in ‘define-values‘ etc.)
Vector
Void
Will
Write
Writer
8.6

Glossary of Racket concepts

Stefan Schwarzer

This glossary is still very much work in progress. Many entries are missing.

1 Introduction

The Racket documentation describes a lot, often very abstract concepts. For someone starting with the language, it’s often not clear which concepts are widely used and which aren’t. It’s quite easy to lose one’s way by getting distracted by relatively obscure concepts.

This led to a discussion on the Racket forum and during this discussion to the plan to start this glossary. The intention is to list many concepts, even the obscure ones, together with their importance, given by these levels:
  • basic: These are basic concepts you should know to write Racket libraries and programs. If you’re starting to learn Racket, focus on these concepts.

    Note that “basic” here doesn’t nessarily mean “trivial.” Don’t be discouraged if you don’t understand these glossary entries immediately. Experiment with the code examples or revisit the respective glossary entry later when you have more Racket experience.

  • intermediate: You can write most Racket software without these features, but you may need them depending on your problem. One example would be threads to execute different tasks concurrently.

  • advanced: Likely you won’t need these features, but they may improve your software. Look into these if you’re comfortable with the entries in the “basic” and “intermediate” categories.

Not all Racket users will agree on this categorization and the assigned levels for individual terms, but the categorization should give you a rough idea which concepts are more foundational than others.

This glossary isn’t a book for learning Racket, nor is it a reference. Some information may be simplified, for example some special cases may not be mentioned. If you want all the details, check out the Racket Reference.

2 Entries

Arity

Level: basic

The arity describes how many arguments a function can accept. Everything from zero to infinitely many arguments is possible. Note that functions can have optional arguments, so even for a specific function, the arity may not be a single number.

Arity refers only to positional arguments, not keyword arguments.

See also:

Assignment

Level: basic

Assigning means the same as in most programming languages: changing the value of a variable. To change the value of a variable, use set!:

Examples:
> (define my-variable 2)
> (displayln my-variable)

2

> (set! my-variable 5)
> (displayln my-variable)

5

However, in Racket and other functional languages, assignment is used much less than in imperative languages. The “normal” approach in functional languages is to transform immutable values to other immutable values.

To change a value via assignment, you need a name (binding) and a storage location for it. Usually, the binding and location are created with define, but they can also be created by one of the let forms.

See also:

Binding

Level: basic

A binding makes a value accessible via a name. Typically, bindings are created with the define form:

Example:
> (define x (+ 2 3))

binds the name x to the value 5.

Note that the bound value is the result of the expression, not the expression itself.

See also:

Boolean

Level: basic

Booleans represent truth values. In some other languages this type is called bool or boolean. Racket has two boolean literals:
  • #f false

  • #t true

If a value is interpreted as a condition (as in if or cond forms), only the constant #f is interpreted as false, all other values are interpreted as true.

Example:
> (for ([value '(#t 1 0 "false" '() map (void) #f)])
    (displayln (if value "true" "false")))

true

true

true

true

true

true

true

false

See also: Booleans in the Racket Reference

Box

Level: intermediate

A box is a container to essentially turn an immutable value into a mutable value. Passing a box as a function argument is similar to passing a value by reference in some other languages.

Examples:
> (define (func value-box)
    (define old-value (unbox value-box))
    (set-box! value-box (add1 old-value)))
> (define a-box (box 7))
> (func a-box)
> (displayln (unbox a-box))

8

As you can see, using boxes is kind of awkward compared to passing arguments by reference in other languages. However, in practice this isn’t a problem since it’s unidiomatic in Racket to use mutable values. Instead you usually transform immutable values to other immutable values.

See also: Boxes in the Racket Reference

Byte string

Level: basic

See String, character, byte string

Call

Level: basic

See Procedure

Channel

Level: intermediate

Chaperone

Level: intermediate

Character

Level: basic

See String, character, byte string

Class

Level: intermediate

Closure

Level: basic

A closure combines a function with environment data from a scope outside the function.

Example:
> (define (make-incrementer increment)
    (lambda (value)
      (+ value increment)))
> (define add3 (make-incrementer 3))
> (add3 5)

8

The return value of make-incrementer is the closure. The lambda expression doesn’t define the increment; the value is taken from the scope outside the lambda expression.

See also:

Collection

Level: basic

Combinator

Level: intermediate

Comprehension

Level: basic

Cons cell

Level: basic

See Pair

Continuation

Level: advanced

Contract

Level: intermediate

Core form

Level: advanced

Currying

Level: basic

See Partial application and currying

Custodian

Level: advanced

Debugging

Level: basic

Definition

Level: basic

Display

Level: basic

DrRacket

Level: basic

DSL (domain-specific language)

Level: advanced

Environment

Level: intermediate

Equality

Level: basic

Scheme and Racket have three generic functions to determine if two values are equal:
  • equal? checks for value equality. Most of the time, this is the function you want. equal? can also compare recursively, as long as the participating types support equal? comparisons. Examples:
    > (equal? (+ 2 3) 5)

    #t

    > (equal? "foo" "foo")

    #t

    > (equal? "foo" "fox")

    #f

    > (equal? '(1 2 3) '(1 2 3))

    #t

    > (equal? '(1 2 ("foo" 3)) '(1 2 ("foo" 3)))

    #t

    > (equal? '(1 2 ("foo" 3)) '(1 2 ("bar" 3)))

    #f

  • eq? checks object identity, i.e. eq? only returns #t if the two compared values are one and the same object. This is especically important for mutable objects. For immutable values object identity is less relevant. Examples:
    ; There's only one `#t` constant.
    > (eq? #t #t)

    #t

    ; Compare with the same list object.
    > (define a-list '(1 2))
    > (eq? a-list a-list)

    #t

    ; Two different list objects
    > (eq? '(1 2) '(1 2))

    #f

  • eqv? behaves mostly like eq?, with the exception of types that have a different implementation. This mainly applies to numbers, but then you probably want to use = anyway.

Unless you need to distinguish between different number types, use = instead of the three functions described above.

The Racket standard library also has many functions of the form type=?, for example string=?. Often these functions are equivalent to equal? for the same arguments. However, using the type=? functions has two advantages:
  • The name makes it clear what’s compared, without looking at surrounding code.

  • Functions of this form provided by Racket check that the arguments have the correct type, e.g. string for string=?.

There’s no == function in Racket, but you could define a function with this name. (But probably you shouldn’t.)

Other languages
The eq? function behaves like the is operator in Python.

See also:

Exact number

Level: basic

Executor

Level: advanced

Exception

Level: intermediate

Expression (always rvalue? may result in one or more values)

Level: basic

Field

Level: basic

See Struct

Fixnum

Level: basic

Flat contract

Level: advanced

Flonum

Level: basic

Fold

Level: basic

Form

Level: basic

Formatting

Level: basic

Level: intermediate

(‘format‘, ‘~a‘ etc.

Function

Level: basic

See Procedure

Functional programming (FP)

Level: basic

Functional update

Level: basic

Compared to an “imperative update,” as used in many programming languages, a “functional update” doesn’t modifiy a value in place, but instead returns a modified copy.

Here’s an example of an imperative update:
; Create a hash table for imperative updates.
> (define imperative-hash (make-hash '((1 . a) (2 . b))))
> imperative-hash

'#hash((1 . a) (2 . b))

; Modify the hash by storing another pair in it.
; The exclamation point at the end of `hash-set!` means
; that the hash is modified in-place.
> (hash-set! imperative-hash 3 'c)
> imperative-hash

'#hash((1 . a) (2 . b) (3 . c))

With an imperative update, every piece of code that has a binding to the hash will see any changes to the hash. Depending on your design, this can be good or bad. In any case you have to be careful that all locations where the hash is used are prepared for a change “under their feet.”

On the other hand, here’s a corresponding functional update:
; Create a hash table for functional updates.
> (define functional-hash (make-immutable-hash '((1 . a) (2 . b))))
> functional-hash

'#hash((1 . a) (2 . b))

; Return a modified copy of the hash.
> (define new-hash (hash-set functional-hash 3 'c))
; The original hash is unchanged.
> functional-hash

'#hash((1 . a) (2 . b))

; The change is only visible in the new value.
> new-hash

'#hash((1 . a) (2 . b) (3 . c))

; Besides, immutable hashes don't allow imperative changes
; (as you probably expected).
> (hash-set! functional-hash 4 'd)

hash-set!: contract violation

  expected: (and/c hash? (not/c immutable?))

  given: '#hash((1 . a) (2 . b))

  argument position: 1st

  other arguments...:

   4

   'd

A functional update has the advantage that you can more easily control which code “sees” which value.

The concept of functional updates doesn’t only apply to hashes, but is very common in Functional Programming in general.

See also:

Future

Level: advanced

Generator

Level: advanced

Generic API

Level: advanced

GUI programming

Level: intermediate

Hash

Level: basic

Hashes, also called hash tables, hash maps or dictionaries, map keys to values. For example, the following hash table maps numbers to symbols:
> (define my-hash
    (hash 1 'a
          2 'b
          3 'c))
; Look up the value for the key 2.
> (hash-ref my-hash 2)

'b

Keys and values can be arbitrary values:
> (define my-hash
    (hash #(1 2 3) '(a b c)
          '(1 2)   #t
          #f       map))
> (hash-ref my-hash #(1 2 3))

'(a b c)

> (hash-ref my-hash '(1 2))

#t

> (hash-ref my-hash #f)

#<procedure:map>

However, usually all keys are of the same type and all values are of the same type.

The API for hashes in Racket is more complicated than for the other compound data structures like lists and vectors. Hashes can differ in the following criteria; all of the combinations are possible:
  • Comparison for keys: equal?, eq?, eqv?

  • Mutability: immutable, mutable
    Mutability applies to the hash as a whole, not to the mutability of the keys or the values.

  • Strength: strong, weak, ephemerons
    This influences when hash entries can be garbage-collected.

These are 3×2×3 = 18 combinations, but in practice you can almost always get by with this list of just four combinations:
  • Comparison for keys: equal?, eq?

  • Mutability: immutable, mutable

  • Strength: strong

Here’s an overview of the most important APIs for these four equality/mutability combinations:

Combination

 

Construction (1, 2)

 

Set or update value (3)

 

Get value

equal?/immutable

 

(hash key1 value1 key2 value2 ...)
or
(make-immutable-hash pair1 pair2 ...)

 

(hash-set hash key value)

 

(hash-ref hash key)

eq?/immutable

 

(hasheq key1 value1 key2 value2 ...)
or
(make-immutable-hasheq pair1 pair2 ...)

 

(hash-set hash key value)

 

(hash-ref hash key)

equal?/mutable

 

(make-hash pair1 pair2 ...)

 

(hash-set! hash key value)

 

(hash-ref hash key)

eq?/mutable

 

(make-hasheq pair1 pair2 ...)

 

(hash-set! hash key value)

 

(hash-ref hash key)

(1) You can create empty hashes by calling the constructor without arguments. For example, (hash) creates an empty immutable hash with equal? key comparison.

(2) A pair here is a regular Scheme/Racket pair, for example (cons 1 'a). Pairs that contain only literals can also be written as '(1 . a).

(3) Setting or updating a value in an immutable hash may sound contradictory. The solution is that hash-set causes a so-called functional update. That is, it returns a new hash with the modification applied and leaves the hash argument unchanged. This is the same principle that cons or struct-copy use.

Warnings:
  • If a hash entry has a mutable key (for example a vector) don’t change the key in-place.

  • Don’t change a hash while iterating over it.

See also:

Higher-order function

Level: basic

Hygiene

Level: intermediate

Identifier (differences to identifiers in other languages)

Level: basic

Identity (also refer to ‘eq?‘)

Level: basic

Impersonator

Level: advanced

Inexact number → Exact number

Level: basic

Inspector

Level: advanced

Interface (API)

Level: basic

Interface (OOP)

Level: intermediate

Keyword arguments (positional and keyword args are separate)

Level: basic

Lambda

Level: basic

As shown in the Procedure entry, a procedure can be defined with define. However, you can also define functions directly as values without giving them a name. This is the same function as in the Procedure glossary entry:

Examples:
> (lambda ([name "world"])
    (string-append "Hello " name "!"))

#<procedure:eval:41:0>

> ((lambda ([name "world"])
     (string-append "Hello " name "!"))
   "Alice")

"Hello Alice!"

The second example above defines and directly calls the function.

The above examples are a bit artifical. Normally, you use a function defined with lambda as a function argument for a Higher-order function or in a Let expression.

See also:

Lang (as in ‘#lang‘)

Level: advanced

Language-oriented programming

Level: advanced

Let

Level: basic

Let over lambda

Level: intermediate

“Let over lambda” describes a common idiom to create a closure. The following example is similar to the one in the Closure entry:
> (define add3
    (let ([increment 3])
      (lambda (value)
        (+ value increment))))
> (add3 5)

8

Here, let creates the outer environent whereas lambda defines the function using that environment.

See also: Procedure, Lambda, Closure in this glossary

List

Level: basic

Lists are the most widely used data structure in many functional programming languages, including Scheme and Racket.

Scheme and Racket lists are implemented as singly-linked lists (with the exception of the empty list, which is an atomic value). Singly-linked lists consist of pairs where the first value of a pair is a list item and the second value of the pair points to the next pair. The end of the list is denoted by an empty list as the second value of the last pair.

For example, the list '(1 2 3 4) can be drawn as

This data structure looks much more complicated than an array, where items are stored in adjacent memory locations. A singly-linked list also has the disadvantage that accessing list items by index requires traversing the list until the pair with the given index is found.

The reason why lists are still so widely used is that they make it easy to prepend items without changing the lists other references “see.” For example, the code
> (define list1 '(1 2 3))
> (define list2 (cons 'a list1))
> (define list3 (cons 'b (cdr list1)))
creates the following data structures:

or, in one picture,

So each of the lists looks as you would expect, but it’s not necessary to make any copies of the list data. Instead the lists share some data, without this being visible to code that uses these lists. (An exception is if list items are changeable values, e.g. vectors, and are actually changed in-place. In that case, all lists that share this changed data “see” the change. But usually you should avoid mutating data, partly for this reason.)

Changing lists with cons maximizes the data that can be shared between lists. The situation is different if you change data “further down” the list. In this case, it may be necessary to copy a part of the list data.

Example:
> (define list1 '(1 2 3 4))
> (define list2 (remove 2 list1))

creates the following structure:

Note that structure sharing depends only on the used list algorithms (e.g. in remove above). There’s no sophisticated algorithm that tries to “de-duplicate” common data. For example,
> (define list1 '(1 2 3))
> (define list2 '(1 2 3))
creates two separate list data structures.

Other languages
Note that Scheme/Racket lists are different from “lists” in many other programming languages. Typically those lists store data in adjacent memory locations and have a fast append operation. Racket lists, on the other hand, are singly-linked lists and have a fast prepend operation (cons), but appending items is rather slow because it has to iterate through the whole list and make a copy. The closest equivalent in Racket to the above-mentioned lists in some languages are called growable vectors (see gvector). However, Racket lists are the idiomatic approach for most list processing, so use them if you can.

See also:

Location

Level: basic

A location is an implicitly created memory location. For example, this happens in define or let forms. A consequence of creating a location is that it can be modified with set! or other mutating functions.

Examples:
> (define (change-argument arg)
    (displayln arg)
    (set! arg 5)
    (displayln arg))
> (change-argument 3)

3

5

> (let ([v (vector 1 2 3)])
    (displayln v)
    (vector-set! v 2 5)
    (displayln v))

#(1 2 3)

#(1 2 5)

However, usually you should avoid mutation in functional programming.

Macro

Level: intermediate

Level: advanced

Match

Level: intermediate

Match transformer

Level: advanced

Method

Level: intermediate

Module

Level: basic

Named let

Level: intermediate

Namespace

Level: intermediate

Naming conventions

Level: basic

As usual for programming languages, Racket code uses some conventions. As with all conventions, there’s no law enforcing them, but you should follow them if you want your code style to look familiar to other Racket programmers. :-)

Parts of identifiers are separated by hypens (kebap case, e.g. vector-length). Snake case (vector_length) or camel case (vectorLength) are not used. Sometimes module-level constants are in all uppercase, for example (define GRID-ROW-COUNT 10). Other names are all lowercase.

Abbreviations are rare; almost everything is “spelled out.” This can result in rather long names like make-input-port/read-to-peek or call-with-default-reading-parameterization. Most names turn out reasonably short, though.

If a name describes a type and an operation on it, the type usually goes first, as in vector-ref or string-copy. The accessor functions generated by struct also follow this pattern.

Additionally, there are some widely-used naming patterns:

Pattern

 

Meaning

 

Examples

name?

 

Predicate (1)

 

string?, null?

name=?

 

Comparison predicate (1)

 

string=?

name!

 

Mutation

 

set!, vector-set!

name*

 

Repetition

 

regexp-replace* vs. regexp-replace

name*

 

Nesting

 

for* vs. for

name*

 

Other variant

 

vector*-length vs. vector-length

name%

 

Class name

 

frame%

part/part

 

“for“ or “with“

 

call/cc, define/match,
for/fold/derived

type->other-type

 

Conversion

 

vector->list

make-type

 

Create new value of type

 

make-base-namespace, make-channel

(1) Since the question mark already identifies a predicate, using prefixes like “is-”, “has-” etc. is redundant.

See also:

Number

Level: basic

Number types

Racket and most Scheme implementations have the following number types:
  • integer

  • rational

  • real

  • complex

The type of a number can be checked with the predicates integer?, rational?, real? and complex?, respectively.

Different from many other programming languages, Scheme/Racket number types don’t directly correspond to how they might be stored on the machine level. Instead, Scheme/Racket number types are closer to the mathematical meanings.

For example, 2 is considered an integer, but so are 2.0 and 6/3:
> (integer? 2)

#t

> (integer? 2.0)

#t

> (integer? 6/3)

#t

All these numbers are mathematically the integer 2.

Besides, in Racket, 6/3 isn’t the operation of dividing 6 by 3, which would be (/ 6 3), but a fraction:
; Simplified and rendered as 2.
> 6/3

2

; Can't be simplified and remains a fraction.
> 6/7

6/7

Here are a few other examples for the number types:
; Any integer number is also a rational number, a real number
; and a complex number.
> (map rational? '(0 1 -2 2.0 6/3))

'(#t #t #t #t #t)

> (map real? '(0 1 -2 2.0 6/3))

'(#t #t #t #t #t)

> (map complex? '(0 1 -2 2.0 6/3))

'(#t #t #t #t #t)

Exactness

Tips

See also:

Numeric tower

Level: basic

Opaque

Level: intermediate

See Struct

Package

Level: intermediate

Pair

Level: basic

Pairs, also called cons cells, are the building blocks of Scheme/Racket lists, but are also often used for combining any two values.

Usually a pair is created with cons:

Example:
> (cons 1 "one")

'(1 . "one")

Once a pair is created, you can access the first and second value of the pair with car and cdr:

Examples:
> (define a-pair (cons 1 "one"))
> (car a-pair)

1

> (cdr a-pair)

"one"

See also:

Parameter

Level: basic

Partial application and currying

Level: basic

Partial application takes a function with some number of arguments and creates a new function that calls the original function while hard-coding some of the arguments.

The following example defines a function draw-line and a curried version draw-line-from-origin, which hard-codes the from-x and from-y arguments:

> (define (draw-line from-x from-y to-x to-y)
    ; Keep it simple; just print a string instead of drawing a line.
    (displayln (format "Drawing a line from (~a, ~a) to (~a, ~a)"
                       from-x from-y to-x to-y)))
> (draw-line 1 2 3 4)

Drawing a line from (1, 2) to (3, 4)

; Hard-code the first two arguments of `draw-line`.
> (define (draw-line-from-origin to-x to-y)
    (draw-line 0 0 to-x to-y))
> (draw-line-from-origin 2 3)

Drawing a line from (0, 0) to (2, 3)

Currying is similar, but it hard-codes only one argument and, as long as the arguments aren’t complete, returns a function that also takes only one argument.

For example, if you have a function curry that does the initial currying, the draw-line example above could become
> (curry draw-line 1)

#<procedure:curried:draw-line>

> ((curry draw-line 1) 2)

#<procedure:curried:draw-line>

> (((curry draw-line 1) 2) 3)

#<procedure:curried:draw-line>

> ((((curry draw-line 1) 2) 3) 4)

Drawing a line from (1, 2) to (3, 4)

Racket has a function curry, which supports both partial application and currying at the same time.
> (curry draw-line 1)

#<procedure:curried:draw-line>

> ((curry draw-line 1) 2)

#<procedure:curried:draw-line>

> (((curry draw-line 1) 2) 3 4)

Drawing a line from (1, 2) to (3, 4)

Note that the last call passes two arguments, 3 and 4.

There’s also a function curryr, which hard-codes arguments from the right instead of the left of the argument list.

Other languages
Unlike Haskell, Scheme/Racket doesn’t have implicit currying, so the following code raises an exception:
> (define draw-line-from-origin (draw-line 0 0))

draw-line: arity mismatch;

 the expected number of arguments does not match the given

number

  expected: 4

  given: 2

See also:

Pattern (in regular expressions)

Level: basic

Pattern (in macro definitions)

Level: intermediate

Phase

Level: advanced

Place

Level: advanced

Polymorphism (rarely used, compare with other languages; see also generic code)

Level: intermediate

Port

Level: basic

Predicate

Level: basic

A predicate is a procedure that takes one argument and returns a boolean.

Examples:

Predicates can be used on their own or as arguments for higher-order functions like filter or takef:
> (filter string? '(2 "foo" bar 7))

'("foo")

> (takef '(2 6 -4 3 5 4) even?)

'(2 6 -4)

If you have a procedure that’s “almost” a predicate, you can use partial application to turn it into a predicate.

Example:
> (define (greater-than-two? number)
    (> number 2))
> (filter greater-than-two? '(1 3 -1 5 2))

'(3 5)

; Equivalent: define an anonymous procedure.
> (filter
    (lambda (number)
      (> number 2))
    '(1 3 -1 5 2))

'(3 5)

See also:

Print

Level: basic

See Display

Procedure

Level: basic

A procedure, also called a function, is a value that can be called at program runtime. If a procedure is used after an opening parenthesis, it introduces a function call. For example, add1 is a simple function that adds 1 to its argument:

Example:
> (add1 2)

3

If a function should be called without arguments, it’s written (func). Since the parentheses cause the function call, you can’t just add brackets around expressions. For example,

Example:
> ((add1 2))

application: not a procedure;

 expected a procedure that can be applied to arguments

  given: 3

calculates 3 through the function call (add1 2) and tries to call the result 3 as a function, (3), which gives an error saying that 3 isn’t callable.

On the other hand, when using a function in another position, it’s just a “passive” value:

Examples:
> add1

#<procedure:add1>

> (procedure? add1)

#t

> (list add1 add1)

'(#<procedure:add1> #<procedure:add1>)

As using too many brackets, using too few can lead to problems, although they tend to be less obvious:

Examples:
> (define (foo)
    add1 2)
> (foo)

2

You might have expected 3 as the result of calling foo. However, since the parentheses around add1 2 were missing and the result of a function is the value of the last expression, the function returned 2.

Depending on how a function is defined, it can take zero arguments to an almost unlimited number of arguments (usually done with a special syntax in the function definition).

The following function takes an optional argument and returns a string to greet someone:

Examples:
> (define (greeting [name "world"])
    (string-append "Hello " name "!"))
> (greeting)

"Hello world!"

> (greeting "Bob")

"Hello Bob!"

See also:

Profiling

Level: intermediate

Prompt

Level: advanced

Provide

Level: intermediate

Quasiquote

Level: intermediate

Quote

Level: basic

RnRS (as in R5RS, R6RS etc.)

Level: intermediate

Raco

Level: basic

Reader (for parsing code)

Level: advanced

Record

Level: basic

See Struct

Require

Level: basic

Rule (in macros; probably other uses, which ones?)

Level: intermediate

Safe operation

Level: intermediate

See Unsafe operation

Scheme

Level: basic

Scribble

Level: intermediate

Sequence

Level: intermediate

Set

Level: intermediate

Shadowing

Level: basic

Splicing

Level: basic

SRFI

Level: intermediate

Standard library

Level: basic

Stream

Level: basic

String, character, byte string

Level: basic

The string, byte string and character data types are used for text processing.

Strings represent human-readable text; they’re what you likely want if you do text processing.

Examples:
; String content enclosed in quotes
> "a string"

"a string"

> (string? "a string")

#t

; A quote inside a string literal can be escaped with a backslash.
> "a string \"with\" quotes"

"a string \"with\" quotes"

> (string-length "\"foo\"")

5

; Use `display` or `displayln` to output a string for human readers.
; `displayln` adds a newline.
> (displayln "a string \"with\" quotes")

a string "with" quotes

; Concatenate strings with `string-append`.
> (string-append "Hello " "world" "!")

"Hello world!"

; Strings can contain non-ASCII characters.
> "Michael Müller"

"Michael Müller"

> (string-length "Michael Müller")

14

A string consists of characters, which represent unicode code points:
; You can get individual characters with `string-ref`.
> (string-ref "Müller" 1)

#\ü

> (char? #\ü)

#t

; Convert a string to a character list and back.
> (string->list "Müller")

'(#\M #\ü #\l #\l #\e #\r)

> (list->string '(#\M #\ü #\l #\l #\e #\r))

"Müller"

That said, usually you don’t need to deal with individual characters. Most string processing works on substrings rather than characters.

To convert a string to bytes that can be written to a file or sent over a network socket, the string needs to be encoded to a byte string:
; Not the only encoding function in the standard library
> (define encoded-string (string->bytes/utf-8 "Müller"))
; ü has been encoded to two bytes.
> encoded-string

#"M\303\274ller"

> (bytes? encoded-string)

#t

Conversely, byte strings can be decoded to strings:
; Not the only decoding function in the standard library
> (bytes->string/utf-8 encoded-string)

"Müller"

Fortunately, as long as the content is in UTF-8 encoding, you don’t need to encode or decode it yourself. For example, file->string decodes the file contents implicitly.

A byte string consists of bytes. “Byte” isn’t a “real” data type, but just means integers from 0 to 255. You can see the bytes/integers in a byte string more clearly with bytes->list:
> (bytes->list encoded-string)

'(77 195 188 108 108 101 114)

The following diagram shows the relationships between the types:

Both strings and byte strings come in mutable and immutable versions. Literals, e.g. "foo" or #"foo", are immutable.

The previous paragraphs explained how the string and character data types in Racket are related. However, for most text processing you’ll be fine with functions that operate on strings. Here are a few of these functions (but also check out the others in Strings in the Racket Reference).

As mentioned above, strings consist of unicode code points. Unicode is rather complex and subtle, so the following caveats are shared by programming languages whose strings consist of code points:
  • The term “character” is ambiguous in the unicode context. It can mean a code point (as in Racket), but also a symbol on the screen that’s perceived as self-contained. The second meaning is sometimes called a “grapheme“ or “grapheme cluster.”

  • The distinction between the meanings of “character” are important because not all grapheme clusters can be expressed in a single code point. For example, the German flag 🇩🇪 consists of two code points:
    > (string->list "🇩🇪")

    '(#\🇩 #\🇪)

  • In some cases, the same grapheme cluster can be expressed in different code point combinations. For example, the string "ü" from above can be expressed in a single code point, but alternatively in two code points, where the first is for the letter "u" and the second for the diacritic (the dots above the u). So two strings that look the same on the screen may not be the same according to Racket’s string=? function, which works on code point sequences.

    The normalization functions can convert strings so that they have the “combined” or the “separate” code points and can be meaningfully compared with string=?.

The above list may sound intimidating, but it isn’t as long as you don’t rely on wrong assumptions (e.g. that the same “character” is always expressed by the same code points). For example, you can safely split a string at newline characters or other ASCII separators like colons without applying any of the normalizations.

See also:

Struct

Level: basic

Although Racket has an OOP system, the primary way to group information for a type is using a struct and functions related to it.

Example:
> (struct person (name age))

Here, person is the name of the struct and name and age are the fields of the struct.

Creating a struct like above creates several bindings. One is for the name of the struct, which can be used to create instances of the struct:

> (define bilbo (person "Bilbo Baggins" 111))
> bilbo

#<person>

Moreover, each field results in a binding named struct-name-field-name to access the value of each field in a given struct instance:

> (person-name bilbo)

"Bilbo Baggins"

> (person-age bilbo)

111

It’s also possible to declare a struct as mutable by adding a #:mutable keyword. This creates additional accessor functions to set values in a struct instance. However, you usually should avoid mutation in Racket. Instead, use struct-copy to create a new struct instance based on another instance:

> (define younger-bilbo (struct-copy person bilbo [age 100]))
> (person-name younger-bilbo)

"Bilbo Baggins"

> (person-age younger-bilbo)

100

See also Functional update. You can use more than one field name/value pair.

By default, structs are created as opaque. This means that printing a struct instance doesn’t show the values. More important is the gotcha that comparing opaque struct instances with equal? compares by identity (like eq?), not by field values!

Example:
> (define bilbo2 (person "Bilbo Baggins" 111))
> (equal? (person-name bilbo) (person-name bilbo2))

#t

> (equal? (person-age bilbo) (person-age bilbo2))

#t

> (equal? bilbo bilbo2)

#f

Adding the #:transparent keyword to the struct definition avoids this problem:

> (struct person (name age)
    #:transparent)
> (define frodo (person "Frodo Baggins" 33))
> frodo

(person "Frodo Baggins" 33)

> (define frodo2 (person "Frodo Baggins" 33))
> (equal? (person-name frodo) (person-name frodo2))

#t

> (equal? (person-age frodo) (person-age frodo2))

#t

> (equal? frodo frodo2)

#t

Note two things:
  • Printing frodo shows the struct values, not just #<person>.

  • Comparing two struct instances of the same transparent struct type with the same values with equal? gives #t.

The latter is not only more intuitive, but it’s also very useful when comparing calculated and expected struct instances in automated tests.

That said, values of different struct types compare as #f, even if the field names are the same.

Although Racket uses opaque structs for stronger encapsulation and backward-compatibility, many Racket users nowadays think that defining structs as transparent usually gives the better tradeoff.

Other languages
Note that field names are only known at compile time, not at runtime. This means that a function like Python’s getattr doesn’t exist in Racket.

See also:

Symbol

Level: basic

Symbols are a data type that doesn’t exist in most programming languages. Symbols are similar to strings:
> "a-string"

"a-string"

> 'a-symbol

'a-symbol

There are several differences, though:
  • Symbols are idiomatically used for enumeration values. See the open-output-file function for an example, which uses symbols for the mode and exists keyword arguments.

  • By default, symbols are interned, while there’s no such guarantee for strings. Interning has the consequence that two “same” symbols compare equal with eq?, not just with equal?. Therefore, hashes that have symbols as keys can use the eq? variants, which speeds up the hash operations.

  • Because of the different uses of strings and symbols, the standard library has no APIs to search in symbols or concatenate them. If you need such functionality, you can convert between symbols and strings with symbol->string and string->symbol.

Symbols occur naturally when processing Racket code as data.

Example:
> (define expr1 (+ 3 4))
> expr1

7

; Note the quote character, '
> (define expr2 '(+ 3 4))
; A list with the `+` symbol, 3 and 4.
> expr2

'(+ 3 4)

> (symbol? (car expr2))

#t

See also:

Syntactic form

Level: basic

See Form

Syntax (different meanings)

Level: intermediate

Syntax transformer

Level: intermediate

See Macro

Tail call

Level: intermediate

Tail position

Level: intermediate

Thread

Level: intermediate

Thunk

Level: basic

Transparent

Level: basic

See Struct

Trust level

Level: advanced

Trusted code

Level: advanced

See Untrusted code

Typed Racket

Level: advanced

Undefined (why do we need this if we have ‘void‘?)

Level: advanced

Unit

Level: advanced

Unsafe operation

Level: intermediate

Untrusted code

Level: advanced

Value (sometimes "object", but may be confused with OOP concept)

Level: basic

Values (multiple values, as in ‘define-values‘ etc.)

Level: basic

Vector

Level: basic

Racket vectors are continuous arrays with indices starting at 0. An advantage of vectors over lists is that accessing a vector item at a random index takes constant time, i.e. is independent of the vector size and the index.

Literal vectors can be written with a #( prefix. Items in literal vectors are automatically quoted. The following examples use the vector-ref function to retrieve an item at a given index.

Examples:
; Simple vector with some numbers.
> #(1 2 3)

'#(1 2 3)

; Empty vector
> #()

'#()

; Items are quoted. This vector does _not_ contain the `map` function.
> (define vec1 #(map))
> (vector-ref vec1 0)

'map

; Use `vector` to avoid quoting.
> (define vec2 (vector map))
> (vector-ref vec2 0)

#<procedure:map>

Vectors can be mutable or immutable. Literal vectors and those created with vector-immutable are immutable. Vectors created with vector are mutable.

Examples:
> (define vec1 #(1 2 3))
> (vector-set! vec1 1 5)

vector-set!: contract violation

  expected: (and/c vector? (not/c immutable?))

  given: '#(1 2 3)

> (define vec2 (vector-immutable 1 2 3))
> (vector-set! vec2 1 5)

vector-set!: contract violation

  expected: (and/c vector? (not/c immutable?))

  given: '#(1 2 3)

> (define vec3 (vector 1 2 3))
> (vector-set! vec3 1 5)
> vec3

'#(1 5 3)

There are several vector functions (e.g. vector-map or vector-filter) that correspond to similar list functions. If the existing vector functions aren’t enough, it may make sense to convert a vector to a list with vector->list, process the list with list functions and finally convert the list back to a vector with list->vector.

See also:

Void

Level: basic

The constant #<void> is used as a return value if there’s no other sensible value. This applies to functions that exist only for their side effects or if no branch in a cond or case form matches.

Experimenting with #<void> can be confusing because the Racket REPL (interactive interpreter) doesn’t show it, but you can check the result for being #<void> with the void? predicate.

Examples:
> (define foo 2)
> (void? (set! foo 3))

#t

> (void? (display ""))

#t

> (void?
    (cond
      [(= 1 2) #f]))

#t

Another curiosity is that you can’t enter the #<void> value in source code or the REPL. However, you can create the value with the void function.

Other languages
Python has a value None, whose semantics is similar to #<void>. However, while the use of None in Python is idiomatic to denote an unset optional argument, Scheme and Racket code typically uses #f for the same purpose.

Example:
> (define (hello [who #f])
    (string-append
      "Hello, "
      (if who who "world")
      "!"))
> (hello)

"Hello, world!"

> (hello "Mike")

"Hello, Mike!"

Since #f is the only value that’s treated as false in conditions, this usage normally makes sense.

That said, it’s fine to use (void) and check the argument with void? if #f could be an actual value for the argument.

See also:

Will

Level: advanced

Write

Level: basic

See Display

Writer

Level: advanced