Beginner

Operators in Smalltalk

Learn how arithmetic, comparison, logical, and string operators work in Smalltalk - where every operator is really a message sent to an object

In most languages, operators are baked into the grammar: + is a special symbol the parser knows about, with its own precedence table. Smalltalk takes a radically different view. There are no operators in the traditional sense at all. 3 + 4 is not “apply the addition operator” - it is “send the message + with argument 4 to the object 3.” The integer 3 decides what + means by looking up the method in its class.

This is the natural consequence of Smalltalk’s core idea that everything is an object and every action is a message. Arithmetic, comparison, logical operations, and even string concatenation are all just binary messages - messages whose selector is a symbol like +, <, or , and which take exactly one argument. Because they are ordinary messages, you can define new ones on your own classes and send the same + to numbers, fractions, or collections.

This tutorial covers the four families of “operators” you will use constantly - arithmetic, comparison, logical, and string concatenation - plus Smalltalk’s famously simple precedence rules. Because operators are messages, understanding them is really understanding how messages are sent, which unlocks the rest of the language.

Arithmetic Operators

Arithmetic operators are binary messages sent to number objects. One detail surprises newcomers: dividing two integers with / yields an exact Fraction, not a truncated or floating-point result. Smalltalk keeps numbers exact unless you ask otherwise.

Create a file named operators_arithmetic.st:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
"Arithmetic operators are just binary messages sent to numbers"
(7 + 3) printNl.            "addition"
(7 - 3) printNl.            "subtraction"
(7 * 3) printNl.            "multiplication"
(7 / 2) printNl.            "division yields an exact Fraction: 7/2"
(7 / 2) asFloat printNl.    "convert the Fraction to a Float: 3.5"
(7 // 2) printNl.           "integer (floor) division"
(7 \\ 2) printNl.           "modulo / remainder"
(2 raisedTo: 8) printNl.    "power: 2 to the 8th"
5 negated printNl.          "unary minus is the message 'negated'"

Notice 5 negated - negation is a unary message, not a - prefix. And (7 / 2) produces 7/2, an exact Fraction object that knows how to print itself. Sending asFloat to it gives the familiar 3.5.

Comparison Operators

Comparison operators are binary messages that return a Boolean object - either true or false. Note that equality is a single = (Smalltalk uses := for assignment, so there is no clash) and inequality is ~=.

Create a file named operators_comparison.st:

1
2
3
4
5
6
7
8
"Comparison operators return Boolean objects (true or false)"
(5 = 5) printNl.        "equality"
(5 ~= 4) printNl.       "inequality (read as 'not equal')"
(3 < 5) printNl.        "less than"
(3 > 5) printNl.        "greater than"
(5 <= 5) printNl.       "less than or equal"
(5 >= 6) printNl.       "greater than or equal"
('abc' = 'abc') printNl. "= compares value: same characters, so true"

The = message compares value (do these objects represent the same thing), so two strings with the same characters are equal. Smalltalk also has ==, which tests identity (are these the exact same object) - a distinction worth remembering, but = is what you want for comparing values.

Logical Operators

Boolean logic is also message-based. The binary messages & (and) and | (or) always evaluate both sides, while the keyword messages and: and or: take a block as their argument and short-circuit - the block only runs if it is needed. not is a unary message.

Create a file named operators_logical.st:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
"Boolean logic through messages"
(true & false) printNl.   "eager logical AND"
(true | false) printNl.   "eager logical OR"
(true not) printNl.       "logical NOT is a unary message"

"Keyword messages with blocks give short-circuit evaluation"
((5 > 3) and: [2 < 4]) printNl.  "right side only runs if left is true"
((5 < 3) or: [2 < 4]) printNl.   "right side only runs if left is false"

"String concatenation is the , (comma) binary message"
('Small' , 'talk') displayNl.

The , (comma) is the binary message for joining collections, including strings. We use displayNl here instead of printNl so the string prints without surrounding quotes.

Operator Precedence

Here is where Smalltalk is wonderfully (and sometimes shockingly) simple. There is no rich precedence table. There are exactly three levels, always applied left to right:

  1. Unary messages first (e.g. factorial, negated)
  2. Binary messages next (e.g. +, *, <, ,)
  3. Keyword messages last (e.g. raisedTo:, and:)

Crucially, all binary operators share the same precedence - * does not bind tighter than +. Use parentheses when you want conventional math precedence.

Create a file named operators_precedence.st:

1
2
3
4
5
6
7
8
9
"All binary operators share one precedence, evaluated left to right"
(3 + 4 * 5) printNl.        "(3 + 4) then * 5  =>  35, NOT 23"
(3 + (4 * 5)) printNl.      "parentheses force multiplication first => 23"

"Unary binds tighter than binary"
(3 + 4 factorial) printNl.  "4 factorial = 24, then 3 + 24 => 27"

"Keyword binds loosest, so the binary 3 + 1 runs first"
(2 raisedTo: 3 + 1) printNl. "3 + 1 = 4, then 2 raisedTo: 4 => 16"

The expression 3 + 4 * 5 evaluating to 35 instead of 23 is the classic Smalltalk gotcha. Once you internalize “binary messages go strictly left to right,” it becomes second nature.

Assignment

Smalltalk uses := for assignment, and that is the only assignment operator - there are no compound forms like += or *=. To accumulate a value, you send the arithmetic message and assign the result back.

Create a file named operators_assignment.st:

1
2
3
4
5
6
7
"Assignment uses := and there are no compound operators like +="
| total |
total := 10.
total := total + 5.    "the += equivalent: add, then reassign"
total printNl.
total := total * 2.
total printNl.

The | total | declares a temporary variable, and each := rebinds it to a new value.

Running with Docker

Run each example with the GNU Smalltalk image. The gst interpreter reads and evaluates a .st file directly.

1
2
3
4
5
6
7
8
9
# Pull the official image
docker pull sl4m/gnu-smalltalk:latest

# Run each operators example
docker run --rm -v $(pwd):/app -w /app sl4m/gnu-smalltalk gst operators_arithmetic.st
docker run --rm -v $(pwd):/app -w /app sl4m/gnu-smalltalk gst operators_comparison.st
docker run --rm -v $(pwd):/app -w /app sl4m/gnu-smalltalk gst operators_logical.st
docker run --rm -v $(pwd):/app -w /app sl4m/gnu-smalltalk gst operators_precedence.st
docker run --rm -v $(pwd):/app -w /app sl4m/gnu-smalltalk gst operators_assignment.st

Expected Output

operators_arithmetic.st:

10
4
21
7/2
3.5
3
1
256
-5

operators_comparison.st:

true
true
true
false
true
false
true

operators_logical.st:

false
true
false
true
true
Smalltalk

operators_precedence.st:

35
23
27
16

operators_assignment.st:

15
30

Key Concepts

  • Operators are messages, not syntax. 3 + 4 sends the binary message + to 3. Any class can implement +, <, or ,, so the same operators work across numbers, fractions, strings, and your own objects.
  • There are only three precedence levels: unary, then binary, then keyword - each evaluated left to right. All binary operators (+, *, <, ,) share one level, so 3 + 4 * 5 is 35. Reach for parentheses to get conventional math ordering.
  • Integer division with / stays exact. 7 / 2 is the Fraction 7/2, not 3 or 3.5. Use // for floor division, \\ for modulo, and asFloat when you want a decimal.
  • Equality is =, inequality is ~=. = compares value; == compares object identity. Assignment is the separate :=, so there is no ==-vs-= confusion as in C-family languages.
  • Logical &/| are eager; and:/or: short-circuit because they take blocks ([ ... ]) that only evaluate when needed. not is a unary message.
  • There are no compound assignment operators. Use total := total + 5 rather than total += 5.
  • Unary messages bind tightest, so 3 + 4 factorial is 27 (the factorial runs before the +).

Running Today

All examples can be run using Docker:

docker pull sl4m/gnu-smalltalk:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining