Scheme is dynamically typed: variables have no types and values carry them. This page lists the types the reports define, how values are compared, and how they are stored and written. Section numbers refer to R7RS-small.
The disjoint types
No object satisfies more than one of these predicates (R7RS section 3.2). Records made by define-record-type are further disjoint types, each with its own predicate.
| Type | Predicate | Written as | Notes |
|---|---|---|---|
| Boolean | boolean? | #t #f | Also #true and #false |
| Number | number? | 42 1/2 3.14 1+2i | The numeric tower; see Procedures and numbers |
| Character | char? | #\a #\space | A Unicode scalar value (an implementation may support a subset) |
| String | string? | "text" | A fixed-length sequence of characters; mutable unless a literal |
| Symbol | symbol? | foo |a b| | A name; two symbols with the same characters are the same object |
| Pair | pair? | (a . b) | Two locations, the car and the cdr |
| The empty list | null? | () | One unique object, not a pair, and not a boolean |
| Vector | vector? | #(1 2 3) | A fixed-length sequence indexed from 0 |
| Bytevector | bytevector? | #u8(1 2 3) | A vector of exact integers from 0 to 255 (R6RS, R7RS) |
| Procedure | procedure? | no standard form | Made by lambda or define, or built in; continuations and parameter objects are procedures |
| Port | port? | no standard form | Textual or binary; input, output or both |
| End-of-file object | eof-object? | made by eof-object | A disjoint type from R7RS |
Other kinds of object are not in the disjoint list but are part of the language: promises (delay, make-promise), parameter objects (make-parameter), environments (environment, interaction-environment), error objects (error-object?) and, in R6RS, conditions, hashtables and enumerations. The unspecified value is what a procedure such as vector-fill! returns when the report gives no value; what it is, and whether it prints, depends on the implementation. A macro keyword is not a value at all: it is an identifier bound to a transformer.
Truth and falsity
Any value can be a test in if, cond, and, or, when, unless and do. Every value counts as true except #f. In particular the empty list, zero, the empty string and #t itself are true; this differs from Lisps in which the empty list is false. The reports call any value but #f true and #f false.
Equivalence
| Procedure | Tests | Examples |
|---|---|---|
eq? | The finest relation, implemented as an identity comparison. Numbers, characters and the empty list are compared by value in practice, but the reports leave much unspecified: (eq? 100000000 100000000) and (eq? "" "") may be either | (eq? 'a 'a) is true; (eq? (list 'a) (list 'a)) is false |
eqv? | Slightly coarser than eq?: the same boolean, the same symbol, exact numbers that are numerically equal, inexact numbers that are equal and behave identically (so 0.0 and -0.0 differ), the same character, the empty list, and the same object for pairs, vectors, bytevectors, records and strings. It tells exact from inexact: (eqv? 2 2.0) is false. Unspecified when both are NaN | (eqv? 2 2) true; (eqv? 0.0 +nan.0) false |
equal? | The coarsest: eqv?, or pairs, vectors, strings and bytevectors whose elements are equal?, recursively, comparing the possibly infinite trees that the structures unfold into; R7RS requires it to terminate even on circular structure. It gives the same answer as eqv? for booleans, symbols, numbers, characters, ports, procedures and the empty list | (equal? "abc" "abc") true; (equal? (make-vector 5 'a) (make-vector 5 'a)) true |
= and the typed comparisons | Numbers by value (=); char=?, string=?, boolean=?, symbol=? for their own types | (= 2 2.0) is true |
The list lookups use them in pairs: memq and assq with eq?, memv and assv with eqv?, member and assoc with equal?, or in R7RS with a third argument that names the comparison.
The storage model
A variable, and each element of a pair, string, vector or bytevector, is a location that holds a value. A new value can be stored into a location with set!, set-car!, string-set! and the like, and a variable or object keeps the same locations afterwards. An object fetched from a location is eqv? to the object last stored there. The reports describe the locations as conceptual: they need not be memory addresses.
Every object that denotes locations is mutable or immutable. Literal constants, and the strings returned by symbol->string, are immutable, and it is an error to store into them. Everything made by the other procedures is mutable. Empty strings, empty vectors and empty bytevectors have no locations and may or may not be allocated anew by each call; the empty list is a single object. R6RS says the same, counts records with no mutable fields as immutable, makes every variable that a library exports immutable (assigning to one is a syntax violation), and keeps the procedures that mutate pairs and strings in the separate libraries (rnrs mutable-pairs) and (rnrs mutable-strings).
(define (f) (list 1 2 3)) ; a fresh list each call
(define g (lambda () '(1 2 3))) ; the same constant each call
(eq? (f) (f)) ; #f
(eq? (g) (g)) ; usually #t, an implementation may do otherwise
(string-set! (symbol->string 'abc) 0 #\x) ; an error: immutable
External representation
An object’s external representation is the text that write produces and read accepts. It is not unique: the integer 28 can be written 28, #e28.000 or #x1c, and the list of 8 and 13 as (8 13), ( 08 13 ) or (8 . (13 . ())). Procedures, ports and several other objects have no standard representation; implementations print things like #<procedure car>, which read cannot read back.
Every expression is also the external representation of a datum: (+ 2 6) is the representation of a three-element list, and is an expression that evaluates to 8. This is why Scheme programs can manipulate programs: quote returns the datum, eval evaluates one, and macros transform them.
R7RS write uses datum labels (#0=) for circular structure and for nothing else; write-shared labels all sharing and write-simple never labels. display writes strings without quotes and characters without #\, and must not loop on circular data. See I/O ports.
Programs are data
Because the syntax is the external representation of lists, a Scheme program is a list of data. Whether a list is code or data depends only on whether it is evaluated. That is the reason the viewer reads a quoted list as data and a plain list as a call: '(if a b) has no keyword in it.
(define expr '(+ 2 6)) ; a list of three elements
(car expr) ; the symbol +
(eval expr (environment '(scheme base))) ; 8
eval).Not described
Known, not yet covered
- Reference: hash tables, enumerations and conditions of R6RS, and the hash tables of SRFI 69, 125 and 126 are named but not described as types; see Standard libraries and SRFI.
- Reference: the types that implementations add (Guile’s keywords and modules, Gauche’s classes, the objects of the foreign-function interfaces) are not described.
See also
- Lexical structure – the written form of each type
- Lists, vectors and strings – the procedures of the sequence types
- Records – making new disjoint types