Beginner

Operators in Dylan

Learn about arithmetic, comparison, logical, and assignment operators in Dylan with practical Docker-ready examples

Operators are the building blocks of expressions in any programming language. In Dylan, operators carry a special significance because of the language’s heritage: they are actually generic functions in disguise. The infix syntax you see — a + b, x < y, n * 2 — is syntactic sugar that the compiler translates into ordinary function calls like \+(a, b) and \<(x, y).

This dual nature is one of Dylan’s most elegant design decisions. It inherits the conceptual uniformity of Lisp (where everything is a function call) while wearing the friendly infix clothing of ALGOL-family languages. Because operators are generic functions, they participate in Dylan’s multiple-dispatch system, meaning + can be specialized for new types you define.

In this tutorial we will explore Dylan’s arithmetic, comparison, logical, and assignment operators, look at how operator precedence works, and see how Dylan’s dynamic typing interacts with arithmetic on mixed numeric types.

Arithmetic Operators

Dylan provides the familiar set of arithmetic operators: +, -, *, /, and unary -. These work across Dylan’s numeric tower (<integer>, <float>, <double-float>, etc.) and automatically perform contagion — when an integer meets a float, the result is a float.

Create a file named arithmetic.dylan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Module: hello

let a = 17;
let b = 5;

format-out("a + b = %d\n", a + b);
format-out("a - b = %d\n", a - b);
format-out("a * b = %d\n", a * b);
format-out("a / b = %d\n", a / b);
format-out("modulo(a, b) = %d\n", modulo(a, b));
format-out("truncate/(a, b) = %d\n", truncate/(a, b));

let mixed = a + 0.5;
format-out("a + 0.5 = %=\n", mixed);

let negated = -a;
format-out("-a = %d\n", negated);

A few Dylan-specific points worth highlighting:

  • Integer division with / truncates toward zero (so 17 / 5 is 3). For explicit truncating or flooring division, use the named functions truncate/ and floor/.
  • Dylan does not have a % operator for remainder. Instead it provides the generic functions modulo and remainder.
  • Numeric contagion promotes the result type: adding <integer> and <float> produces a <float>.

Comparison and Logical Operators

Comparison operators in Dylan return #t (true) or #f (false). Equality has two flavours: = for value equality (which Dylan calls similarity) and == for identity (the same object in memory).

Logical combinators are written & for AND and | for OR, and they short-circuit. Negation is the function ~ (often pronounced “not”). One quirk worth knowing up front: in Dylan, only #f is false — every other value, including 0, the empty string "", and the empty list #(), is truthy.

Create a file named comparison.dylan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Module: hello

let x = 10;
let y = 20;

format-out("x = y?  %=\n", x = y);
format-out("x < y?  %=\n", x < y);
format-out("x <= y? %=\n", x <= y);
format-out("x > y?  %=\n", x > y);
format-out("x ~= y? %=\n", x ~= y);

let s1 = "hello";
let s2 = "hello";
format-out("s1 = s2 (similar)?  %=\n", s1 = s2);
format-out("s1 == s2 (identical)? %=\n", s1 == s2);

let in-range? = (x > 0) & (x < 100);
let extreme?  = (x < 0) | (x > 1000);
format-out("0 < x < 100? %=\n", in-range?);
format-out("extreme x?   %=\n", extreme?);
format-out("not extreme: %=\n", ~extreme?);

format-out("0 truthy?  %s\n", if (0) "yes" else "no" end);
format-out("\"\" truthy? %s\n", if ("") "yes" else "no" end);
format-out("#f truthy? %s\n", if (#f) "yes" else "no" end);

Notice the trailing ? on identifier names like in-range? and extreme?. Dylan, in the Lisp tradition, allows ? (and !, -, *) inside identifier names. It is a strong convention to suffix predicates with ?.

Assignment and Mutation

Dylan distinguishes carefully between binding (introducing a name with let or define) and assignment (changing what an existing mutable binding refers to). Assignment uses the := operator, not = — because = is reserved for the equality predicate.

Create a file named assignment.dylan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
Module: hello

let counter = 0;
format-out("initial counter = %d\n", counter);

counter := counter + 1;
counter := counter + 1;
counter := counter * 10;
format-out("after updates  = %d\n", counter);

let total = 100;
total := total - 25;
format-out("total = %d\n", total);

let v = make(<vector>, size: 3, fill: 0);
v[0] := 7;
v[1] := 8;
v[2] := 9;
format-out("vector contents: %d %d %d\n", v[0], v[1], v[2]);

The last block illustrates an important point: v[0] := 7 is not a special form built into the compiler. It is shorthand for the generic function call element-setter(7, v, 0). Any container that defines an element-setter method automatically participates in this syntax. This is the same uniformity that lets you redefine + for your own classes.

Operator Precedence and Function-Style Calls

Dylan has a deliberately flat precedence model compared with C or Java. Arithmetic operators bind tighter than comparison operators, which bind tighter than the logical combinators & and | — but within arithmetic, * and / have the same precedence as + and - and are evaluated strictly left to right. This catches programmers from C-family languages by surprise, so when in doubt: parenthesize.

Because every operator is also a function, you can call them in prefix form by escaping the name with a backslash.

Create a file named precedence.dylan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Module: hello

let r1 = 2 + 3 * 4;
let r2 = 2 + (3 * 4);
let r3 = (2 + 3) * 4;
format-out("2 + 3 * 4   = %d (Dylan: left-to-right)\n", r1);
format-out("2 + (3 * 4) = %d\n", r2);
format-out("(2 + 3) * 4 = %d\n", r3);

let prefix-sum     = \+(10, 20);
let prefix-compare = \<(5, 9);
format-out("\\+(10, 20)  = %d\n", prefix-sum);
format-out("\\<(5, 9)    = %=\n", prefix-compare);

let nums = #(1, 2, 3, 4, 5);
let total = reduce(\+, 0, nums);
format-out("sum of #(1,2,3,4,5) via reduce(\\+, ...) = %d\n", total);

The last example is the punchline of Dylan’s operator design: because + is just a function, you can pass it to reduce like any other value. Functional patterns fall out naturally.

Running with Docker

1
2
3
4
5
6
7
8
# Pull the Dylan Docker image
docker pull codearchaeology/dylan:latest

# Run each example
docker run --rm -v $(pwd):/app codearchaeology/dylan:latest dylan arithmetic.dylan
docker run --rm -v $(pwd):/app codearchaeology/dylan:latest dylan comparison.dylan
docker run --rm -v $(pwd):/app codearchaeology/dylan:latest dylan assignment.dylan
docker run --rm -v $(pwd):/app codearchaeology/dylan:latest dylan precedence.dylan

Expected Output

Running arithmetic.dylan:

a + b = 22
a - b = 12
a * b = 85
a / b = 3
modulo(a, b) = 2
truncate/(a, b) = 3
a + 0.5 = 17.5
-a = -17

Running comparison.dylan:

x = y?  #f
x < y?  #t
x <= y? #t
x > y?  #f
x ~= y? #t
s1 = s2 (similar)?  #t
s1 == s2 (identical)? #f
0 < x < 100? #t
extreme x?   #f
not extreme: #t
0 truthy?  yes
"" truthy? yes
#f truthy? no

Running assignment.dylan:

initial counter = 0
after updates  = 20
total = 75
vector contents: 7 8 9

Running precedence.dylan:

2 + 3 * 4   = 20 (Dylan: left-to-right)
2 + (3 * 4) = 14
(2 + 3) * 4 = 20
\+(10, 20)  = 30
\<(5, 9)    = #t
sum of #(1,2,3,4,5) via reduce(\+, ...) = 15

Key Concepts

  • Operators are generic functions. Every infix operator in Dylan desugars to a function call, so operators participate in multiple dispatch and can be specialized for user-defined classes.
  • = is equality, := is assignment. This separation removes a whole class of C-style bugs and frees = to be a useful predicate.
  • Two equalities: = and ==. = tests similarity (value equality) and is the one you usually want; == tests identity (same object).
  • Only #f is false. Zero, the empty string, and the empty list are all truthy. Predicates always return #t or #f explicitly.
  • No % remainder operator. Use the generic functions modulo and remainder — they differ in how they handle negative numbers.
  • Precedence is flat within arithmetic. * and / do not bind tighter than + and -; evaluation is strict left-to-right. Parenthesize when mixing.
  • Logical & and | short-circuit and return the actual value that decided the expression — not just a boolean — so they double as Dylan’s “or-else” and “and-then” combinators.
  • Operators are values. You can pass \+, \*, \< to higher-order functions like reduce, map, and choose, which is where Dylan’s functional heritage shines through.

Running Today

All examples can be run using Docker:

docker pull codearchaeology/dylan:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining