NexusCS

Smalltalk

Languages
Smalltalk is a pure object-oriented programming language where everything is an object and computation happens through message passing. Its syntax is famously minimal—it fits on a postcard.
object-oriented
dynamic
pharo
squeak

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:

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