Getting started
Introduction
Smalltalk is a dynamically-typed, reflective language with a live coding environment. Everything is an object, and all computation is done by sending messages to objects.
Common implementations:
- Pharo - Modern, actively developed
- Squeak - Educational, multimedia-focused
- GNU Smalltalk - Command-line oriented
Basic syntax
"Comments are in double quotes"
"Assignment"
x := 42.
"Return value"
^ x + 10
"Multiple statements (separated by period)"
x := 1.
y := 2.
^ x + y
The entire Smalltalk syntax fits on a postcard: objects, messages, blocks, and assignment.
Message passing
"Unary message (no arguments)"
'hello' size. "Returns 5"
42 negated. "Returns -42"
"Binary message (operators)"
3 + 4. "Returns 7"
10 * 5. "Returns 50"
'Hello' , ' World'. "Returns 'Hello World'"
"Keyword message (named parameters)"
'Hello' at: 1. "Returns $H"
10 max: 20. "Returns 20"
Array with: 1 with: 2. "Returns #(1 2)"
Messages are evaluated left-to-right: unary first, then binary, then keyword.
Variables and blocks
Local variables
"Declare local variables between pipes"
| |
| --- |
| x y result |
x := 10.
y := 20.
result := x + y.
^ result
Variables must be declared before use in a | var1 var2 | block.
Blocks (closures)
"Simple block"
[ 'Hello World' ] value.
"Block with parameters"
[ :x | x * 2 ] value: 5. "Returns 10"
[ :x :y | x + y ] value: 3 value: 5. "Returns 8"
"Blocks with local variables"
[ | temp |
temp := 5.
temp * 2
] value.
Blocks are closures—they capture their lexical environment and can be passed around as objects.
Message cascade
"Without cascade - multiple statements"
stream := WriteStream on: String new.
stream nextPutAll: 'Hello'.
stream nextPutAll: ' World'.
stream contents.
"With cascade - same receiver"
WriteStream on: String new
nextPutAll: 'Hello';
nextPutAll: ' World';
contents.
The semicolon ; sends multiple messages to the same receiver, avoiding repetition.
Control structures
Conditionals
"If-then"
x > 10
ifTrue: [ 'Greater' ]
ifFalse: [ 'Not greater' ].
"If-then without else"
x > 0 ifTrue: [ self doSomething ].
"Chained conditions"
x = 0
ifTrue: [ 'zero' ]
ifFalse: [
x > 0
ifTrue: [ 'positive' ]
ifFalse: [ 'negative' ]
].
Control structures are implemented as messages sent to Boolean objects.
Loops
"Times repeat"
5 timesRepeat: [ Transcript show: 'Hello'; cr ].
"To-do (counting)"
1 to: 10 do: [ :i | Transcript show: i printString; cr ].
"While loops"
[ x < 100 ] whileTrue: [ x := x * 2 ].
"Until loops"
[ x > 100 ] whileFalse: [ x := x + 1 ].
Loops are messages sent to blocks or numbers.
Iteration over collections
"Iterate over collection"
#(1 2 3 4 5) do: [ :each | Transcript show: each printString ].
"With index"
#('a' 'b' 'c') doWithIndex: [ :item :idx |
Transcript show: idx printString , ': ' , item; cr
].
"Nested iteration"
#(1 2 3) do: [ :x |
#(10 20 30) do: [ :y |
Transcript show: (x * y) printString; cr
]
].
Collections
Arrays
"Literal array"
#(1 2 3 4 5).
"Dynamic array"
Array with: 1 with: 2 with: 3.
{ 1 + 2. 3 * 4. 5 }. "Evaluates expressions"
"Access elements (1-indexed)"
arr := #(10 20 30).
arr at: 1. "Returns 10"
arr at: 2 put: 25. "Sets element"
arr first. "Returns 10"
arr last. "Returns 30"
arr size. "Returns 3"
Arrays are fixed-size and 1-indexed.
OrderedCollection
"Create ordered collection"
col := OrderedCollection new.
col add: 'first'.
col add: 'second'.
col addFirst: 'beginning'.
col addLast: 'end'.
"Access elements"
col at: 1. "Returns 'beginning'"
col size. "Returns 4"
"Remove elements"
col remove: 'second'.
col removeFirst.
col removeLast.
OrderedCollection is dynamic—it grows and shrinks automatically.
Set and Dictionary
"Set (unique elements)"
set := Set new.
set add: 1; add: 2; add: 2; add: 3.
set size. "Returns 3"
set includes: 2. "Returns true"
"Dictionary (key-value pairs)"
dict := Dictionary new.
dict at: 'name' put: 'Alice'.
dict at: 'age' put: 30.
dict at: 'name'. "Returns 'Alice'"
dict includesKey: 'age'. "Returns true"
dict keys. "Returns all keys"
dict values. "Returns all values"
Bag
"Bag (multiset - allows duplicates)"
bag := Bag new.
bag add: 'apple'; add: 'orange'; add: 'apple'.
bag occurrencesOf: 'apple'. "Returns 2"
bag size. "Returns 3"
Collection operations
Transforming
"Map (collect)"
#(1 2 3 4 5) collect: [ :x | x * 2 ].
"Returns #(2 4 6 8 10)"
"Filter (select)"
#(1 2 3 4 5) select: [ :x | x even ].
"Returns #(2 4)"
"Reject"
#(1 2 3 4 5) reject: [ :x | x even ].
"Returns #(1 3 5)"
Searching
"Detect (find first)"
#(1 2 3 4 5) detect: [ :x | x > 3 ].
"Returns 4"
"Detect with default"
#(1 2 3) detect: [ :x | x > 10 ] ifNone: [ 0 ].
"Returns 0"
"Any/all"
#(2 4 6) allSatisfy: [ :x | x even ]. "Returns true"
#(1 2 3) anySatisfy: [ :x | x > 5 ]. "Returns false"
Reducing
"Inject (reduce/fold)"
#(1 2 3 4 5) inject: 0 into: [ :sum :each | sum + each ].
"Returns 15"
"Product"
#(1 2 3 4) inject: 1 into: [ :product :each | product * each ].
"Returns 24"
"Sum (built-in)"
#(1 2 3 4 5) sum. "Returns 15"
Sorting and grouping
"Sort"
#(3 1 4 1 5 9) sorted.
"Returns #(1 1 3 4 5 9)"
"Sort with custom comparator"
#('zebra' 'apple' 'mango') sorted: [ :a :b | a < b ].
"Group by"
#(1 2 3 4 5 6) groupedBy: [ :x | x even ].
"Returns dictionary: false->#(1 3 5), true->#(2 4 6)"
Classes and methods
Defining a class
"Pharo/Squeak syntax"
Object subclass: #Person
instanceVariableNames: 'name age'
classVariableNames: ''
package: 'MyApp'
Classes are created by sending messages to superclasses.
Instance methods
"Getter method"
Person >> name
^ name
"Setter method"
Person >> name: aString
name := aString
"Method with logic"
Person >> isAdult
^ age >= 18
"Method with multiple arguments"
Person >> name: aName age: anAge
name := aName.
age := anAge
Methods are defined using the ClassName >> methodName syntax in the browser.
Class methods
"Class-side method"
Person class >> new
^ super new initialize
"Factory method"
Person class >> named: aName
^ self new name: aName
"Usage"
person := Person named: 'Alice'.
Class methods are defined on the class side (metaclass).
self and super
"self - refers to current object"
Person >> greet
^ 'Hello, I am ' , self name
"super - refers to superclass implementation"
Employee >> initialize
super initialize.
salary := 0.
Advanced features
Exception handling
"Basic try-catch"
[ 1 / 0 ] on: Error do: [ :ex |
Transcript show: 'Error: ' , ex messageText; cr.
0
].
"Specific exceptions"
[ self doSomething ]
on: ZeroDivide
do: [ :ex | ex return: 0 ].
"Signal (raise) an exception"
Error signal: 'Something went wrong'.
"Custom exception"
ZeroDivide signal.
Ensure and ifCurtailed
"Ensure - always executes cleanup"
file := FileStream fileNamed: 'data.txt'.
[ file contents ]
ensure: [ file close ].
"ifCurtailed - executes only if interrupted"
[ self longOperation ]
ifCurtailed: [ self cleanup ].
Reflection
"Class of object"
42 class. "Returns SmallInteger"
'hello' class. "Returns ByteString"
"Responds to message?"
42 respondsTo: #sqrt. "Returns true"
"Perform message dynamically"
42 perform: #sqrt. "Returns 6.48..."
42 perform: #+ with: 8. "Returns 50"
"List all methods"
Person selectors. "Returns array of method names"
Person allInstVarNames. "Returns instance variables"
Common patterns
Factory pattern
"Factory method"
Person class >> named: aName age: anAge
^ self new
name: aName;
age: anAge;
yourself.
"Usage"
person := Person named: 'Bob' age: 25.
The yourself message returns the receiver, useful after message cascades.
Singleton pattern
"Class variable for singleton"
MyClass class >> uniqueInstance
UniqueInstance ifNil: [ UniqueInstance := self new ].
^ UniqueInstance
"Reset"
MyClass class >> reset
UniqueInstance := nil.
Visitor pattern
"Accept method in visitee"
Node >> accept: aVisitor
^ aVisitor visitNode: self
"Visitor"
Visitor >> visitNode: aNode
"Process node"
aNode children do: [ :child | child accept: self ].
Builder pattern
PersonBuilder >> new
^ super new initialize
PersonBuilder >> name: aString
person name: aString.
^ self
PersonBuilder >> age: anInteger
person age: anInteger.
^ self
PersonBuilder >> build
^ person
"Usage"
person := PersonBuilder new
name: 'Alice';
age: 30;
build.
Examples
FizzBuzz
"FizzBuzz using conditionals"
1 to: 100 do: [ :n |
(n \\ 15 = 0)
ifTrue: [ Transcript show: 'FizzBuzz' ]
ifFalse: [
(n \\ 3 = 0)
ifTrue: [ Transcript show: 'Fizz' ]
ifFalse: [
(n \\ 5 = 0)
ifTrue: [ Transcript show: 'Buzz' ]
ifFalse: [ Transcript show: n printString ]
]
].
Transcript cr
].
Fibonacci
"Recursive Fibonacci"
Integer >> fibonacci
self <= 1 ifTrue: [ ^ self ].
^ (self - 1) fibonacci + (self - 2) fibonacci
"Usage"
10 fibonacci. "Returns 55"
"Iterative with inject"
fibonacciUpTo: n
^ (1 to: n) inject: #(0 1) into: [ :fib :i |
{ fib second. fib first + fib second }
] first.
Class with initialization
"Define class"
Object subclass: #BankAccount
instanceVariableNames: 'balance owner'
classVariableNames: ''
package: 'Banking'
"Initialize"
BankAccount >> initialize
super initialize.
balance := 0.
"Methods"
BankAccount >> deposit: amount
balance := balance + amount.
BankAccount >> withdraw: amount
balance >= amount
ifTrue: [ balance := balance - amount ]
ifFalse: [ self error: 'Insufficient funds' ].
BankAccount >> balance
^ balance
"Usage"
account := BankAccount new.
account deposit: 100.
account withdraw: 30.
account balance. "Returns 70"
Simple web server (Pharo)
"Using Zinc HTTP Components"
ZnServer startDefaultOn: 8080.
ZnServer default
onRequestRespond: [ :request |
ZnResponse ok: (ZnEntity html: '<h1>Hello from Smalltalk!</h1>')
].
"Stop server"
ZnServer stopDefault.
Gotchas
Precedence of messages
"Wrong - binary evaluated first"
3 + 4 * 5. "Returns 35, not 23"
"Correct - use parentheses"
3 + (4 * 5). "Returns 23"
"Unary > Binary > Keyword"
2 raisedTo: 1 + 3. "Returns 16 (not 5)"
2 raisedTo: (1 + 3). "Returns 16"
Smalltalk evaluates left-to-right within precedence levels. Use parentheses for math expressions.
Arrays are 1-indexed
"First element is at index 1"
arr := #(10 20 30).
arr at: 0. "Error!"
arr at: 1. "Returns 10"
"Use first/last for clarity"
arr first. "Returns 10"
arr last. "Returns 30"
Unlike most modern languages, Smalltalk uses 1-based indexing.
Assignment vs equality
"Assignment"
x := 5. "Assigns 5 to x"
"Equality comparison"
x = 5. "Returns true"
x == 5. "Returns true (identity)"
"Common mistake"
if x := 5 [...] "Wrong! This assigns, not compares"
if x = 5 [...] "Correct comparison"
Use := for assignment, = for equality, == for identity.
Blocks don't auto-return
"Wrong - no return value"
[ :x | x * 2 ]
"Correct - explicit return"
[ :x | ^ x * 2 ] "Returns from method"
"Or just leave as last expression"
[ :x | x * 2 ] "Block evaluates to result"
The ^ operator returns from the enclosing method, not just the block.
Period after statements
"Wrong - missing period"
x := 5
y := 10
"Correct"
x := 5.
y := 10.
"Last statement can omit period"
x := 5.
y := 10
Statements must be separated by periods (except the last one in a method).
Mutable strings
"Strings are mutable by default"
str := 'hello'.
str at: 1 put: $H. "Changes to 'Hello'"
"Use copy for immutability"
original := 'hello'.
modified := original copy.
modified at: 1 put: $H.
"original is still 'hello'"
Also see
- Pharo by Example - Free comprehensive book
- Pharo Official Site - Modern Smalltalk implementation
- Squeak Official Site - Educational Smalltalk
- GNU Smalltalk - Command-line Smalltalk
- Smalltalk Syntax on a Postcard - Complete syntax reference
- GitHub Issue #686 - Original request