Operators in Scheme
Learn how Scheme handles arithmetic, comparison, logical, and equality operators using uniform prefix notation and its exact numeric tower
In most languages, operators are special syntax: 2 + 3 reads left to right, and a precedence table decides whether 2 + 3 * 4 means 14 or 20. Scheme throws all of that away. There are no operators in the syntactic sense — +, <, and = are ordinary procedures, and you call them exactly like any other procedure, in prefix position inside parentheses.
This is a direct consequence of Scheme’s heritage as a minimalist Lisp. Everything is an S-expression of the form (operator operand ...), so (+ 2 3) is a procedure call where + happens to be the procedure. Because they are just procedures, the arithmetic and comparison “operators” are variadic — (+ 1 2 3 4 5) is perfectly valid — and there is no precedence to memorize. The parentheses make evaluation order explicit and unambiguous.
In this tutorial you’ll work through Scheme’s arithmetic procedures (including its exact rational number tower), its comparison and logical operations, and the family of equality predicates that distinguishes Scheme from most languages. Along the way you’ll see why prefix notation and the “only #f is false” rule make Scheme’s expression model unusually consistent.
Arithmetic Operators
The four arithmetic procedures +, -, *, and / accept any number of arguments. A standout feature is Scheme’s numeric tower: dividing two integers that don’t divide evenly yields an exact rational, not a truncated or floating-point result.
Create a file named arithmetic.scm:
| |
The key surprises for newcomers: (/ 4) returns 1/4 (single-argument division is reciprocal), (- 5) negates, and (/ 1 3) produces the exact fraction 1/3 rather than 0.333.... The difference between remainder and modulo only shows with negative numbers — remainder takes the sign of the dividend, modulo takes the sign of the divisor.
Comparison Operators
Comparison procedures return a boolean (#t or #f). Like the arithmetic procedures, they are variadic — and this is genuinely useful: (< 1 2 3 4) checks that the whole sequence is strictly increasing in a single call.
Create a file named comparison.scm:
| |
Note that = is specifically for numbers. Scheme has no !=; you express “not equal” by wrapping a comparison in not. The variadic chaining means (< 1 2 2 4) is #f because 2 < 2 fails somewhere in the chain.
Logical Operators
and, or, and not handle boolean logic, but and and or are special forms, not ordinary procedures — they short-circuit, evaluating only as many arguments as needed. They also return useful values, not just booleans: and returns its last operand when everything is truthy, and or returns the first truthy operand it finds.
Scheme’s truthiness rule is famously strict: only #f is false. Every other value — including 0 and the empty list '() — counts as true.
Create a file named logical.scm:
| |
Because and returns its last value, (and config-loaded? user-name) can be used to retrieve user-name only when the configuration is present — a common Scheme idiom that replaces null checks. The empty forms (and) and (or) return their identity elements, #t and #f respectively.
Equality Predicates
Where most languages have one ==, Scheme offers a family of equality predicates, each suited to a different job. Choosing the right one matters: eq? tests object identity, eqv? adds reliable comparison for numbers and characters, and equal? does a deep structural comparison that works on lists and strings.
Create a file named equality.scm:
| |
A useful rule of thumb: use equal? when comparing compound data like lists and strings, = when comparing numbers, and eq?/eqv? for symbols and identity checks. Strings even have their own comparison procedures (string=?, string<?, and friends) for lexicographic ordering, and string-append plays the role that a + concatenation operator does elsewhere.
Precedence and Nesting
Because operators are just procedures wrapped in parentheses, Scheme has no operator precedence at all. There is nothing to memorize and no ambiguity — the parentheses dictate exactly what evaluates first. Translating infix math into Scheme is purely a matter of nesting.
Create a file named operators.scm:
| |
The two contrasting expressions show the point precisely: (* (+ 2 3) 4) is 20 while (+ 2 (* 3 4)) is 14. No precedence table is needed because the structure is already in the parentheses.
Running with Docker
| |
Expected Output
Running arithmetic.scm:
7
7
42
5
15
24
-5
1/4
1/3
3/4
3
2
2
1024
4
42
9
1
Running comparison.scm:
#t
#t
#f
#t
#t
#t
#f
#t
#t
Running logical.scm:
#t
#f
#t
#t
3
5
true
true
#t
#f
Running equality.scm:
#t
#t
#t
#t
#t
#t
#t
#t
Hello, World!
Running operators.scm:
20
14
17
#t
#t
Key Concepts
- Operators are procedures —
+,<, and=are ordinary procedures called in prefix position, not special syntax. - No precedence rules — parentheses make evaluation order explicit, so
(* (+ 2 3) 4)is unambiguous with nothing to memorize. - Variadic by default —
(+ 1 2 3 4 5)and chained comparisons like(< 1 2 3 4)work because the procedures accept any number of arguments. - Exact numeric tower —
(/ 1 3)produces the exact fraction1/3, not a floating-point approximation; integers stay exact unless you introduce a float. quotient,remainder, andmodulohandle integer division, withremainderfollowing the dividend’s sign andmodulofollowing the divisor’s.- Only
#fis false —0,"", and'()are all truthy, which trips up programmers coming from other languages. and/orreturn values and short-circuit — they yield the last (or first truthy) operand, enabling them to stand in for conditional value selection.- Pick the right equality predicate —
=for numbers,equal?for deep structural comparison of lists and strings,eq?/eqv?for identity and symbols.
Running Today
All examples can be run using Docker:
docker pull weinholt/guile:latest
Comments
Loading comments...
Leave a Comment