← Back to all projects

LEARN SMALLTALK DEEP DIVE

Learn Smalltalk: From Zero to Mastery

Goal: Deeply understand Smalltalk—from message passing fundamentals to building your own virtual machine. Master the language that invented modern OOP, live programming, and IDE concepts.


Why Smalltalk Matters

Smalltalk isn’t just another programming language—it’s the origin story of modern software development:

  • Invented Object-Oriented Programming as we know it (1972-1980, Xerox PARC)
  • Created the first modern IDE with integrated debugger, browser, and inspector
  • Pioneered the GUI (windows, menus, mice—all came from Smalltalk)
  • Introduced Design Patterns (most patterns were first implemented in Smalltalk)
  • Inspired Java, Ruby, Python, Objective-C, and virtually every OO language

But Smalltalk is more than history—it’s a radically different way of thinking about programming:

  1. Everything is an object—numbers, classes, methods, even true and false
  2. All computation is message passing—no function calls, no operators, just messages
  3. Live environment—you modify a running system, not dead text files
  4. Minimal syntax—the entire language fits on a postcard
  5. Reflective—the system can inspect and modify itself

After completing these projects, you will:

  • Think in objects and messages, not functions and data
  • Understand how Smalltalk’s simplicity enables incredible power
  • Be able to build any application in a live, interactive environment
  • Understand exactly how OOP languages work under the hood
  • Appreciate why Smalltalk developers are so passionate about their language

Choosing Your Smalltalk Dialect

Before starting, you need to choose a Smalltalk implementation. I recommend Pharo for learning:

Dialect Best For Notes
Pharo Learning & Modern Development Most active community, best docs, modern IDE
Squeak Educational, Etoys, Historical Original open-source Smalltalk
Cuis Minimal, Clean, Understanding Core Stripped-down, easier to understand internals
GNU Smalltalk Command-line scripting File-based, less “live”
VisualWorks Enterprise Commercial, industrial strength

Start with Pharo and use the Pharo Launcher to manage images.


Core Concept Analysis

The Fundamental Truth: Everything is Messages

3 + 4           "Send message '+' to object 3 with argument 4"
'hello' size    "Send message 'size' to string 'hello'"
5 factorial     "Send message 'factorial' to 5"

There are only three types of messages:

  1. Unary: 5 factorial (no arguments)
  2. Binary: 3 + 4 (one argument, special characters)
  3. Keyword: array at: 1 put: 'hello' (one or more arguments with colons)

Syntax on a Postcard

exampleMethod: arg1 with: arg2
    "Comment describing the method"
    | localVar |
    localVar := arg1 + arg2.         "Assignment uses :="
    localVar > 10                     "Comparison returns Boolean object"
        ifTrue: [ ^ 'big' ]          "Blocks in square brackets"
        ifFalse: [ ^ 'small' ].      "^ means return"
    ^ localVar                        "Implicit return of self if no ^"

Six Reserved Words

  • self - the receiver of the current message
  • super - the receiver, but method lookup starts in superclass
  • nil - the undefined object
  • true / false - the boolean objects
  • thisContext - the current execution context (stack frame)

Blocks (Closures)

"A block is a first-class closure"
[:x | x * 2]         "Block with one parameter"

"Control structures are just messages to blocks"
x > 0 ifTrue: [self positive] ifFalse: [self negative]

"The above is: send 'ifTrue:ifFalse:' to boolean object with two block arguments"

Project List

Projects progress from understanding fundamentals to building sophisticated systems. Each builds on concepts from previous projects.


Project 1: Interactive Calculator REPL

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, Cuis-Smalltalk, GNU Smalltalk
  • Coolness Level: Level 2: Practical but Forgettable
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: Message Passing / Object Fundamentals
  • Software or Tool: Pharo
  • Main Book: “Pharo by Example” by Stéphane Ducasse et al.

What you’ll build: A command-line calculator that parses mathematical expressions and evaluates them using pure message sending, demonstrating how even basic arithmetic is object-oriented in Smalltalk.

Why it teaches Smalltalk: This forces you to understand that 3 + 4 isn’t calling an operator—it’s sending the message + to the object 3 with argument 4. You’ll see how numbers are real objects with methods.

Core challenges you’ll face:

  • Understanding message precedence → maps to unary > binary > keyword, left to right
  • Parsing expressions into message sends → maps to Smalltalk’s unique syntax
  • Exploring Number’s protocol → maps to discovering methods like sqrt, sin, factorial
  • Handling errors gracefully → maps to exception handling with on:do:

Key Concepts:

  • Message Syntax: “Pharo by Example” Chapter 3 - Stéphane Ducasse
  • Number Hierarchy: “Pharo by Example” Chapter 8 - Understanding Magnitude, Number, Integer, Float
  • Exception Handling: “Pharo by Example” Chapter 11
  • Workspace and Playground: Pharo Documentation - Using the interactive tools

Difficulty: Beginner Time estimate: Weekend Prerequisites: Download and install Pharo. Complete the built-in ProfStef tutorial (ProfStef go in Playground). Basic programming concepts (variables, loops).

Real world outcome:

Smalltalk Calculator v1.0
> 5 factorial
120
> 3.14159 sin
0.00000265...
> 2 raisedTo: 10
1024
> 100 sqrt
10.0
> (3 + 4) * 2
14
> exit
Goodbye!

Implementation Hints:

Start by exploring the Number class hierarchy in the System Browser. Try these in the Playground:

  • 3 class → SmallInteger
  • 3 class superclass → Integer
  • 3 class allSuperclasses → See the full hierarchy

Your calculator should:

  1. Read input from a ReadStream or Stdio stdin
  2. Parse the expression (start simple: just evaluate Smalltalk expressions directly using Compiler evaluate:)
  3. Print the result
  4. Loop until user types ‘exit’

Questions to answer as you build:

  • What happens when you divide by zero? How does Smalltalk handle it?
  • What’s the difference between 3/4 and 3.0/4.0?
  • Why does 1/3 * 3 give you 1 and not 0.999...?

Learning milestones:

  1. You evaluate expressions in Playground → You understand the basic environment
  2. You browse the Number class → You understand how to explore the system
  3. You build a working REPL → You understand input/output and basic control flow
  4. You add error handling → You understand exception handling in Smalltalk

Project 2: Personal Address Book with Persistence

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, Cuis-Smalltalk, VisualWorks
  • Coolness Level: Level 2: Practical but Forgettable
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: Object Modeling / Collections / Persistence
  • Software or Tool: Pharo
  • Main Book: “Pharo by Example” by Stéphane Ducasse et al.

What you’ll build: A contact management system where you create Person objects, store them in collections, search/filter them, and persist the entire object graph by saving the Smalltalk image.

Why it teaches Smalltalk: This introduces you to defining your own classes, understanding instance variables, and seeing how the Smalltalk image provides “persistence for free”—when you save the image, all your objects survive.

Core challenges you’ll face:

  • Defining a class with instance variables → maps to understanding class definitions
  • Creating accessor methods → maps to Smalltalk’s encapsulation model
  • Using collections effectively → maps to OrderedCollection, Set, Dictionary
  • Understanding the image → maps to live objects persist when image is saved

Key Concepts:

  • Class Definition: “Pharo by Example” Chapter 5 - Creating your first class
  • Collections: “Pharo by Example” Chapter 9 - Understanding Smalltalk collections
  • Object Serialization: “Deep into Pharo” Chapter on Fuel (for external persistence)
  • Instance vs Class Variables: “Pharo by Example” Chapter 5

Difficulty: Beginner Time estimate: Weekend Prerequisites: Project 1 completed. Understanding of object-oriented basics (classes, instances).

Real world outcome:

"In Playground:"
| book john jane |
book := AddressBook new.
john := Person name: 'John Doe' email: 'john@example.com' phone: '555-1234'.
jane := Person name: 'Jane Smith' email: 'jane@example.com' phone: '555-5678'.

book addContact: john.
book addContact: jane.

book searchByName: 'john'.  "Returns collection with john"
book allContacts size.       "Returns 2"

"Save image - all objects persist!"
Smalltalk saveSession.

"Restart Pharo - your AddressBook is still there with all contacts!"

Implementation Hints:

Create a Person class:

  1. Open System Browser (Cmd+O, Cmd+B)
  2. Create a new package ‘AddressBook’
  3. Define class: Object subclass: #Person instanceVariableNames: 'name email phone' ...
  4. Generate accessors (right-click → Refactoring → Accessor)

Create an AddressBook class:

  • Has instance variable contacts (an OrderedCollection)
  • Methods: addContact:, removeContact:, searchByName:, allContacts

For searching, explore Smalltalk’s powerful collection methods:

  • select: - filter elements matching condition
  • detect: - find first matching element
  • collect: - transform each element

Questions to explore:

  • What happens if you try to add the same person twice? Should you use a Set?
  • How would you implement searchByName: using select:?
  • Try saving with Fuel for external file persistence—what’s different?

Learning milestones:

  1. You create your first class → You understand class definition syntax
  2. You use collections with blocks → You understand Smalltalk’s functional style
  3. You save and restore the image → You understand image-based persistence
  4. You add Fuel export → You understand external serialization

Project 3: Control Flow Demystifier

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, Cuis-Smalltalk, GNU Smalltalk
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Blocks / Closures / Metaprogramming
  • Software or Tool: Pharo
  • Main Book: “Pharo by Example” by Stéphane Ducasse et al.

What you’ll build: Implement your own control structures (myIfTrue:ifFalse:, myWhileTrue:, myTimesRepeat:) by understanding how Smalltalk implements them with blocks and message passing.

Why it teaches Smalltalk: This is the “aha moment” for most developers. In Smalltalk, if isn’t a keyword—it’s a message sent to a Boolean. while isn’t special syntax—it’s a message sent to a Block. Understanding this reveals Smalltalk’s elegant simplicity.

Core challenges you’ll face:

  • Understanding blocks are objects → maps to blocks respond to value, value:, etc.
  • Implementing conditionals → maps to True and False are different classes
  • Creating loops from recursion → maps to blocks can recursively send value to themselves
  • Understanding lazy evaluation → maps to blocks delay evaluation until value is sent

Key Concepts:

  • BlockClosure: “Pharo by Example” Chapter 6 - Understanding blocks
  • Boolean Implementation: Browse True and False classes in System Browser
  • Lazy Evaluation: “Structure and Interpretation of Computer Programs” (for theoretical background)
  • Higher-Order Messages: “Pharo by Example” Chapter 6

Difficulty: Intermediate Time estimate: 1 week Prerequisites: Projects 1-2 completed. Understanding of recursion. Familiarity with closures from other languages (helpful but not required).

Real world outcome:

"Your implementations work exactly like the built-in ones:"

(5 > 3) myIfTrue: [ 'yes' ] myIfFalse: [ 'no' ].  "Returns 'yes'"

| count |
count := 0.
[ count < 5 ] myWhileTrue: [
    Transcript show: count; cr.
    count := count + 1
].
"Prints 0 1 2 3 4"

5 myTimesRepeat: [ Transcript show: 'Hello!'; cr ].
"Prints Hello! five times"

Implementation Hints:

First, browse the actual implementation:

  1. Open System Browser
  2. Find class True → look at method ifTrue:ifFalse:
  3. Find class False → look at same method
  4. Notice: True evaluates the first block, False evaluates the second!

For your implementation:

  • Extend Boolean with myIfTrue:myIfFalse: (actually, extend True and False separately)
  • In True >> myIfTrue:myIfFalse:, evaluate the first block: trueBlock value
  • In False >> myIfTrue:myIfFalse:, evaluate the second block: falseBlock value

For loops, extend BlockClosure:

  • myWhileTrue: recursively calls itself if condition still true
  • Be careful with infinite loops while debugging!

For timesRepeat:, extend Integer:

  • Could use recursion: if receiver > 0, evaluate block, then send (self - 1) myTimesRepeat: aBlock

Questions to ponder:

  • Why must ifTrue:ifFalse: take blocks, not evaluated expressions?
  • What would happen if you wrote (5 > 3) ifTrue: 'yes' ifFalse: 'no'?
  • How does Smalltalk avoid infinite recursion in whileTrue:?

Learning milestones:

  1. You browse True and False classes → You understand Boolean is just objects
  2. You implement myIfTrue:myIfFalse: → You understand polymorphism solves conditionals
  3. You implement myWhileTrue: → You understand recursive block evaluation
  4. You can explain why blocks are needed → You understand lazy evaluation

Project 4: Collection Framework Explorer

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, Cuis-Smalltalk, GNU Smalltalk
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Collections / Design Patterns / Class Hierarchy
  • Software or Tool: Pharo
  • Main Book: “Pharo by Example” by Stéphane Ducasse et al.

What you’ll build: Implement your own mini-collection framework from scratch—MyCollection, MyOrderedCollection, MySet, MyDictionary—with methods like do:, select:, collect:, inject:into:.

Why it teaches Smalltalk: Smalltalk’s collection framework is a masterpiece of object-oriented design. The Template Method pattern, Iterator pattern, and internal iterators all originated here. Building your own reveals how elegant API design emerges from simple principles.

Core challenges you’ll face:

  • Designing a class hierarchy → maps to understanding inheritance and polymorphism
  • Implementing internal iterators → maps to do: as the foundation of everything
  • Understanding Template Method pattern → maps to abstract methods in base class
  • Hash-based collections → maps to implementing = and hash properly

Key Concepts:

  • Collection Hierarchy: “Pharo by Example” Chapter 9
  • Template Method Pattern: “Design Patterns” by Gang of Four, Chapter 5
  • Internal vs External Iterators: “Design Patterns” by Gang of Four
  • Object Equality: “Pharo with Style” - Implementing = and hash

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Projects 1-3 completed. Basic understanding of data structures (arrays, linked lists, hash tables).

Real world outcome:

| myList mySet myDict |

myList := MyOrderedCollection new.
myList add: 1; add: 2; add: 3; add: 4; add: 5.

myList select: [:x | x even].        "Returns MyOrderedCollection(2 4)"
myList collect: [:x | x * 2].        "Returns MyOrderedCollection(2 4 6 8 10)"
myList inject: 0 into: [:sum :x | sum + x].  "Returns 15"

mySet := MySet new.
mySet add: 'apple'; add: 'banana'; add: 'apple'.
mySet size.  "Returns 2 (no duplicates)"

myDict := MyDictionary new.
myDict at: 'name' put: 'Alice'.
myDict at: 'age' put: 30.
myDict at: 'name'.  "Returns 'Alice'"

Implementation Hints:

Start with the hierarchy:

MyCollection (abstract)
├── MySequenceableCollection (abstract, has order)
│   └── MyOrderedCollection (growable array)
└── MySet (no duplicates, uses hashing)
    └── MyDictionary (key-value pairs)

The key insight: do: is the primitive operation. Everything else builds on it:

"In MyCollection (abstract):"
do: aBlock
    self subclassResponsibility  "Each subclass implements differently"

select: aBlock
    | result |
    result := self species new.
    self do: [:each |
        (aBlock value: each) ifTrue: [result add: each]].
    ^ result

collect: aBlock
    | result |
    result := self species new.
    self do: [:each | result add: (aBlock value: each)].
    ^ result

For MyOrderedCollection:

  • Use an Array internally, grow when needed
  • Implement do: by iterating through the array

For MySet:

  • Use an Array of “buckets” for hash table
  • Objects go in bucket determined by hash \\ buckets size
  • Use = to check for existing elements

Questions to explore:

  • What is species and why is it important for subclasses?
  • How do you implement detect:ifNone:?
  • Why must you override both = and hash together?

Learning milestones:

  1. You implement do: for each collection type → You understand the Template Method pattern
  2. You build select: and collect: using do: → You understand internal iterators
  3. You implement a working hash table → You understand hashing and equality
  4. You handle edge cases (empty collections, not found) → You understand robust API design

Project 5: Unit Testing Framework (Mini-SUnit)

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, Cuis-Smalltalk, GNU Smalltalk
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Reflection / Testing / Design Patterns
  • Software or Tool: Pharo
  • Main Book: “Pharo by Example” by Stéphane Ducasse et al.

What you’ll build: Create your own testing framework inspired by SUnit (the original xUnit that inspired JUnit, NUnit, pytest, etc.), including test discovery, assertions, test suites, and result reporting.

Why it teaches Smalltalk: SUnit was invented in Smalltalk by Kent Beck. Building it teaches reflection (finding test methods automatically), the Composite pattern (test suites), and how Smalltalk’s uniformity makes meta-programming trivial.

Core challenges you’ll face:

  • Discovering test methods via reflection → maps to asking a class for its methods
  • Running methods by name → maps to perform: message
  • Collecting results → maps to understanding Composite pattern
  • Handling test failures vs errors → maps to exception handling

Key Concepts:

  • SUnit Framework: “Pharo by Example” Chapter 7
  • Reflection: “Pharo by Example” Chapter 14 - Reflection
  • Composite Pattern: “Design Patterns” by Gang of Four
  • perform: message: Pharo documentation on dynamic message sending

Difficulty: Intermediate Time estimate: 1 week Prerequisites: Projects 1-4 completed. Understanding of how unit tests work conceptually.

Real world outcome:

"Define a test case:"
MyTestCase subclass: #CalculatorTest
    instanceVariableNames: 'calc'.

CalculatorTest >> setUp
    calc := Calculator new.

CalculatorTest >> testAddition
    self assert: (calc add: 2 to: 3) equals: 5.

CalculatorTest >> testDivision
    self assert: (calc divide: 10 by: 2) equals: 5.

CalculatorTest >> testDivideByZero
    self should: [calc divide: 1 by: 0] raise: ZeroDivide.

"Run the tests:"
MyTestRunner run: CalculatorTest.

"Output:"
CalculatorTest
  testAddition: PASSED
  testDivision: PASSED
  testDivideByZero: PASSED
3 run, 3 passed, 0 failed

Implementation Hints:

Core classes to build:

  1. MyTestCase - base class for all tests
  2. MyTestResult - collects passes, failures, errors
  3. MyTestSuite - runs multiple test cases
  4. MyTestRunner - discovers and executes tests

For test discovery:

"Find all methods starting with 'test'"
testMethods := aTestCaseClass methods
    select: [:m | m selector beginsWith: 'test'].

For running a test method by name:

"Create instance and run method dynamically"
instance := aTestCaseClass new.
instance perform: #testAddition.

For assertions:

assert: aBoolean
    aBoolean ifFalse: [MyTestFailure signal: 'Assertion failed'].

assert: actual equals: expected
    self assert: actual = expected.

should: aBlock raise: anExceptionClass
    [aBlock value.
     MyTestFailure signal: 'Expected exception not raised']
        on: anExceptionClass
        do: [:ex | "Good, exception was raised"].

Questions to explore:

  • How do you distinguish between a test failure (assertion failed) and an error (unexpected exception)?
  • How would you implement setUp and tearDown?
  • How can you run tests from multiple classes as a suite?

Learning milestones:

  1. You discover methods via reflection → You understand Smalltalk’s meta-level
  2. You run methods dynamically with perform: → You understand late binding
  3. You handle failures vs errors → You understand exception types
  4. You create a test suite → You understand the Composite pattern

Project 6: Morphic Drawing Application

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, Cuis-Smalltalk
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: GUI / Event Handling / Morphic Framework
  • Software or Tool: Pharo + Morphic
  • Main Book: “Pharo by Example” by Stéphane Ducasse et al.

What you’ll build: A simple drawing application using Morphic where users can create shapes (rectangles, circles, lines), move them around, change colors, and save/load drawings.

Why it teaches Smalltalk: Morphic is the GUI framework that comes with Pharo/Squeak—it’s a live, direct-manipulation environment where UI objects are first-class citizens. Understanding Morphic means understanding how Smalltalk pioneered the graphical interfaces we use every day.

Core challenges you’ll face:

  • Understanding Morph hierarchy → maps to everything visible is a Morph
  • Handling mouse events → maps to event handling protocol
  • Composing Morphs → maps to Morphs contain other Morphs
  • Custom drawing → maps to overriding drawOn:

Key Concepts:

  • Morphic Basics: “Pharo by Example” Chapter 12 - Morphic
  • Event Handling: “An Introduction to Morphic” (INRIA PDF)
  • Custom Morphs: “The Morph Book vol. I” (Cuis documentation, concepts apply to Pharo)
  • Halos and Direct Manipulation: Pharo documentation on Morphic halos

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Projects 1-5 completed. Basic understanding of event-driven programming.

Real world outcome: A windowed application where you can:

  • Click a toolbar button to select a shape type (rectangle, ellipse, line)
  • Click and drag on the canvas to create shapes
  • Click existing shapes to select them
  • Drag shapes to move them
  • Use a color picker to change shape colors
  • Save drawings to a file and load them back

Implementation Hints:

Start by exploring Morphic:

"In Playground - create a simple morph"
| m |
m := Morph new.
m color: Color red.
m extent: 100@50.
m openInWorld.  "Opens it on screen!"

"Try getting halos: Cmd+click (or Alt+click) on the red box"

Your application structure:

  1. DrawingCanvas - a PasteUpMorph or Morph subclass that holds shapes
  2. DrawingShape - abstract superclass for shapes
  3. RectangleShape, EllipseShape, LineShape - concrete shapes
  4. DrawingToolbar - buttons for tool selection

For custom drawing:

MyCircleShape >> drawOn: aCanvas
    aCanvas
        fillOval: self bounds
        color: self color
        borderWidth: 2
        borderColor: Color black.

For mouse events:

MyCanvas >> mouseDown: evt
    self isDrawingMode ifTrue: [
        startPoint := evt position.
        "Begin creating shape..."
    ].

MyCanvas >> mouseMove: evt
    "Update shape preview..."

MyCanvas >> mouseUp: evt
    "Finalize shape creation..."

Questions to explore:

  • How do Morphs handle their bounds (position and size)?
  • What’s the difference between extent, bounds, and position?
  • How do you make a Morph respond to keyboard events?

Learning milestones:

  1. You create and display a custom Morph → You understand Morphic basics
  2. You handle mouse events → You understand event-driven programming in Smalltalk
  3. You compose Morphs (canvas contains shapes) → You understand Morph composition
  4. You implement drag and drop → You understand interactive Morphic programming

Project 7: Text Adventure Game Engine

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, Cuis-Smalltalk, GNU Smalltalk
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Domain Modeling / State Machines / Parsing
  • Software or Tool: Pharo
  • Main Book: “Smalltalk-80: The Language and Its Implementation” (Blue Book)

What you’ll build: An interactive fiction engine where rooms, objects, and NPCs are modeled as Smalltalk objects. The player types commands like “go north”, “take lamp”, “talk to wizard” which are parsed and dispatched as messages to game objects.

Why it teaches Smalltalk: Text adventures map beautifully to OOP—rooms, items, and characters are naturally objects. Parsing commands into messages demonstrates how Smalltalk’s message-passing model can represent any domain elegantly.

Core challenges you’ll face:

  • Domain modeling with objects → maps to creating a rich class hierarchy
  • Parsing natural language commands → maps to string manipulation and simple parsing
  • State management → maps to player inventory, room connections, object states
  • Double dispatch → maps to “use key on door” requires both objects to cooperate

Key Concepts:

  • Domain Modeling: “Object-Oriented Software Construction” by Bertrand Meyer, Chapter 9
  • State Pattern: “Design Patterns” by Gang of Four
  • Command Pattern: “Design Patterns” by Gang of Four
  • Simple Parsing: “Pharo by Example” Chapter 10 - Streams

Difficulty: Intermediate Time estimate: 2 weeks Prerequisites: Projects 1-6 completed. Familiarity with text adventure games helpful.

Real world outcome:

Welcome to the Castle of Doom!

You are standing in a dark entrance hall. Cobwebs hang from the ceiling.
There is a rusty KEY on the ground.
Exits: north, east

> look
You are standing in a dark entrance hall. Cobwebs hang from the ceiling.
There is a rusty KEY on the ground.
Exits: north, east

> take key
You pick up the rusty key.

> inventory
You are carrying:
  - a rusty key

> go north
You walk north into a long corridor.
There is a locked DOOR at the end.
Exits: south

> use key on door
The key fits! The door swings open.
You can now go north.

> go north
Congratulations! You've escaped the Castle of Doom!
*** YOU WIN ***

Implementation Hints:

Core classes:

GameObject (abstract)
├── Room (has exits, contains items)
├── Item (can be taken, used)
│   ├── Key
│   └── Lamp
├── Character (can be talked to)
│   └── Wizard
└── Player (has inventory, current room)

Rooms connect to each other:

entranceHall := Room new
    name: 'Entrance Hall';
    description: 'A dark entrance hall with cobwebs.'.

corridor := Room new
    name: 'Corridor';
    description: 'A long corridor with a door.'.

entranceHall connectNorth: corridor.
corridor connectSouth: entranceHall.

Command parsing approach:

parseCommand: input
    | words verb object |
    words := input asLowercase substrings.
    verb := words first.

    verb = 'go' ifTrue: [^ self goDirection: words second].
    verb = 'take' ifTrue: [^ self takeItem: words second].
    verb = 'look' ifTrue: [^ self look].
    "etc..."

For “use X on Y”, you need double dispatch:

Key >> useOn: aDoor
    aDoor unlockWith: self.

Door >> unlockWith: aKey
    (aKey fits: self lock)
        ifTrue: [self unlock]
        ifFalse: ['The key doesn''t fit.']

Learning milestones:

  1. You model rooms and connections → You understand object relationships
  2. You parse commands → You understand string processing in Smalltalk
  3. You implement inventory → You understand collections in a real context
  4. You handle complex interactions (use X on Y) → You understand double dispatch

Project 8: Method Finder and Code Browser

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, Cuis-Smalltalk
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Reflection / Metaprogramming / IDE Tooling
  • Software or Tool: Pharo
  • Main Book: “Deep into Pharo” by Stéphane Ducasse et al.

What you’ll build: A tool that lets you search for methods by example—you give input and expected output, and it finds methods that produce that transformation. Plus a mini class browser that shows class hierarchies, methods, and source code.

Why it teaches Smalltalk: Smalltalk’s reflection capabilities are extraordinary—you can ask any object what class it is, ask any class what methods it has, and even get the source code of any method. This project demonstrates why Smalltalk’s environment is so powerful for exploration.

Core challenges you’ll face:

  • Iterating over all classes → maps to Smalltalk allClasses
  • Finding methods by example → maps to trying methods and checking results
  • Getting method source code → maps to CompiledMethod sourceCode
  • Understanding class/metaclass duality → maps to every class is an instance of its metaclass

Key Concepts:

  • Reflection: “Deep into Pharo” Chapter 14
  • Metaclasses: “Pharo by Example” Chapter 13
  • CompiledMethod: Browse the CompiledMethod class
  • System Dictionary: Understanding Smalltalk globals

Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Projects 1-7 completed. Comfort with Smalltalk’s syntax and environment.

Real world outcome:

"Method Finder:"
MethodFinder findMethod: 'hello' -> 'HELLO'.
"Returns: #(asUppercase)"

MethodFinder findMethod: #(3 1 4 1 5) -> #(1 1 3 4 5).
"Returns: #(sorted asSortedCollection)"

MethodFinder findMethod: 'hello' -> 5.
"Returns: #(size)"

"Code Browser:"
| browser |
browser := MyCodeBrowser new.
browser browseClass: OrderedCollection.

"Opens window showing:"
OrderedCollection
  Superclass: SequenceableCollection
  Instance variables: array, firstIndex, lastIndex

  Methods:
    - add:  "Add an element..."
    - addFirst:  "Add to beginning..."
    - removeFirst  "Remove and return first..."
    ...

  [Click any method to see source code]

Implementation Hints:

For MethodFinder, iterate over classes and their methods:

findMethodFor: input expecting: output
    | results |
    results := OrderedCollection new.

    Smalltalk allClasses do: [:class |
        class methods do: [:method |
            (method numArgs = 0) ifTrue: [
                "Try calling it on the input"
                [| result |
                 result := input perform: method selector.
                 result = output ifTrue: [results add: method selector]
                ] on: Error do: [:e | "Ignore errors"]
            ]
        ]
    ].
    ^ results

For the Code Browser:

"Get class info"
aClass superclass.                    "Parent class"
aClass instVarNames.                  "Instance variable names"
aClass methods.                       "All methods"
aClass class methods.                 "Class-side methods"

"Get method info"
aMethod selector.                     "Method name"
aMethod sourceCode.                   "Source as string"
aMethod literals.                     "Constants used"

For displaying hierarchies:

"Print class hierarchy"
printHierarchy: aClass indent: level
    Transcript
        show: (String new: level * 2 withAll: $ );
        show: aClass name;
        cr.
    aClass subclasses do: [:sub |
        self printHierarchy: sub indent: level + 1
    ].

Questions to explore:

  • What’s the difference between aClass methods and aClass methodDict?
  • How do you find all senders of a particular message?
  • What is a CompiledMethod and how does it relate to source code?

Learning milestones:

  1. You iterate over all classes → You understand the system dictionary
  2. You find methods by example → You understand reflection and perform:
  3. You display source code → You understand CompiledMethod
  4. You show class hierarchies → You understand the metaclass system

Project 9: Simple HTTP Server

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, GNU Smalltalk
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Networking / Protocols / Concurrent Programming
  • Software or Tool: Pharo + Zinc HTTP Components
  • Main Book: “Enterprise Pharo: A Web Perspective” by Stéphane Ducasse et al.

What you’ll build: A basic HTTP/1.1 server from scratch that can serve static files, handle GET and POST requests, and route URLs to handler methods. Then explore Seaside, Smalltalk’s innovative web framework.

Why it teaches Smalltalk: Building a server demonstrates socket programming in Smalltalk, concurrent request handling with processes, and how Smalltalk’s object model applies to web development. Seaside then shows you a radically different approach to web apps.

Core challenges you’ll face:

  • Socket programming → maps to understanding streams and sockets
  • Parsing HTTP requests → maps to text processing and protocols
  • URL routing → maps to mapping paths to handlers
  • Concurrent connections → maps to Smalltalk processes (green threads)

Key Concepts:

  • Sockets: “Enterprise Pharo” Chapter on Zinc
  • HTTP Protocol: RFC 2616 (HTTP/1.1 specification)
  • Processes: “Pharo by Example” Chapter 15
  • Seaside: “Dynamic Web Development with Seaside” (free book)

Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Projects 1-8 completed. Basic understanding of HTTP protocol.

Real world outcome:

"Start server:"
server := MyHttpServer new.
server
    onGet: '/hello' do: [:req |
        MyHttpResponse ok: 'Hello, World!'];
    onGet: '/time' do: [:req |
        MyHttpResponse ok: DateAndTime now asString];
    onPost: '/echo' do: [:req |
        MyHttpResponse ok: req body];
    serveDirectory: '/static' from: '/path/to/files'.

server startOn: 8080.
"Server running on http://localhost:8080"

"Test it:"
$ curl http://localhost:8080/hello
Hello, World!

$ curl http://localhost:8080/time
2025-01-15T10:30:45-05:00

$ curl -X POST -d "Testing!" http://localhost:8080/echo
Testing!

Implementation Hints:

Basic server loop:

startOn: port
    | serverSocket |
    serverSocket := Socket newTCP.
    serverSocket listenOn: port.

    [true] whileTrue: [
        | clientSocket |
        clientSocket := serverSocket waitForAcceptFor: 60.
        clientSocket ifNotNil: [
            "Handle in separate process"
            [self handleConnection: clientSocket] fork
        ]
    ]

Parsing HTTP request:

"HTTP request looks like:"
"GET /path HTTP/1.1\r\n"
"Host: localhost:8080\r\n"
"\r\n"

parseRequest: aString
    | lines firstLine parts |
    lines := aString lines.
    firstLine := lines first.
    parts := firstLine substrings.

    ^ MyHttpRequest new
        method: parts first;     "GET"
        path: parts second;      "/"path"
        version: parts third.    "HTTP/1.1"

Building HTTP response:

MyHttpResponse class >> ok: body
    ^ self new
        status: 200;
        statusText: 'OK';
        body: body;
        header: 'Content-Type' value: 'text/plain';
        header: 'Content-Length' value: body size asString.

asString
    ^ String streamContents: [:s |
        s << 'HTTP/1.1 ' << status << ' ' << statusText << String crlf.
        headers keysAndValuesDo: [:k :v |
            s << k << ': ' << v << String crlf].
        s << String crlf.
        s << body]

After building from scratch, explore Zinc (Pharo’s built-in HTTP library) and Seaside (component-based web framework).

Learning milestones:

  1. You accept socket connections → You understand network programming in Smalltalk
  2. You parse HTTP requests → You understand protocol implementation
  3. You serve static files → You understand file I/O
  4. You explore Seaside → You understand Smalltalk’s unique approach to web apps

Project 10: Object Database (OODB)

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, GNU Smalltalk
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Persistence / Serialization / Transactions
  • Software or Tool: Pharo
  • Main Book: “Deep into Pharo” by Stéphane Ducasse et al.

What you’ll build: A simple object database that persists entire object graphs to disk, handles object identity across saves/loads, supports simple queries, and provides basic transaction semantics.

Why it teaches Smalltalk: The image already provides persistence, but what about external storage? This project explores how to serialize complex object graphs (handling cycles, shared references) and why OODBs are so natural in Smalltalk.

Core challenges you’ll face:

  • Serializing object graphs → maps to handling cycles and shared references
  • Maintaining object identity → maps to == vs =, identity hash
  • Lazy loading → maps to proxies and doesNotUnderstand:
  • Transaction semantics → maps to copy-on-write or journaling

Key Concepts:

  • Object Serialization: “Deep into Pharo” - Fuel framework
  • Object Identity: “Pharo by Example” Chapter 8
  • Proxy Pattern: “Design Patterns” by Gang of Four
  • doesNotUnderstand:: “Pharo by Example” Chapter 14

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 1-9 completed. Understanding of serialization concepts.

Real world outcome:

"Create database"
db := MyObjectDatabase open: 'mydata.oodb'.

"Store objects"
john := Person name: 'John' age: 30.
jane := Person name: 'Jane' age: 28.
john spouse: jane.
jane spouse: john.  "Circular reference!"

db store: john as: 'john'.
db store: jane as: 'jane'.
db commit.

"Later, in a fresh image..."
db := MyObjectDatabase open: 'mydata.oodb'.
john := db at: 'john'.
john name.           "Returns 'John'"
john spouse name.    "Returns 'Jane'"
john spouse spouse == john.  "Returns true - identity preserved!"

"Query"
adults := db query: [:p | p age >= 18].

Implementation Hints:

Object serialization must handle:

  1. Primitive types: Integers, Floats, Strings, Symbols
  2. Collections: Each element must be serialized
  3. Circular references: Need to track already-serialized objects
  4. Object identity: Same object serialized once, referenced by ID

Approach:

"Assign each object a unique OID"
serialize: anObject
    (self alreadySerialized: anObject) ifTrue: [
        ^ self writeReference: (self oidFor: anObject)
    ].

    self assignOid: anObject.
    self writeClassName: anObject class name.
    anObject class instVarNames do: [:var |
        | value |
        value := anObject instVarNamed: var.
        self serialize: value  "Recursive!"
    ].

For lazy loading, use a Proxy:

Object subclass: #LazyProxy
    instanceVariableNames: 'db oid realObject'.

LazyProxy >> doesNotUnderstand: aMessage
    realObject ifNil: [realObject := db loadObject: oid].
    ^ aMessage sendTo: realObject.

The become: primitive can swap the proxy for the real object once loaded.

Learning milestones:

  1. You serialize simple objects → You understand instVarNamed: access
  2. You handle circular references → You understand object identity
  3. You implement lazy loading → You understand doesNotUnderstand:
  4. You add transactions → You understand atomicity and consistency

Project 11: Visual Debugger

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, Cuis-Smalltalk
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Execution Model / Contexts / Debugger Internals
  • Software or Tool: Pharo
  • Main Book: “Deep into Pharo” by Stéphane Ducasse et al.

What you’ll build: A visual debugger that shows the call stack, lets you step through code, inspect variables, and even modify running code and continue execution—demonstrating Smalltalk’s legendary debugging capabilities.

Why it teaches Smalltalk: Smalltalk’s debugger is famous for allowing you to fix bugs in running code. The key is thisContext—a first-class object representing the current execution state. Understanding contexts means understanding how Smalltalk execution really works.

Core challenges you’ll face:

  • Understanding Context objects → maps to stack frames are objects!
  • Implementing step/step-into/step-over → maps to manipulating contexts
  • Displaying stack trace → maps to walking the context chain
  • Hot code replacement → maps to modifying methods in a running process

Key Concepts:

  • Context and Execution: “Deep into Pharo” Chapter on Debugging
  • thisContext: “Pharo by Example” Chapter 14
  • Process Model: “Deep into Pharo” Chapter 15
  • Block Contexts: Understanding BlockClosure in relation to MethodContext

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 1-10 completed. Solid understanding of Smalltalk execution model.

Real world outcome:

"Trigger debugger on error:"
1/0.  "Opens your MyDebugger!"

"Or set breakpoint:"
MyDebugger breakpointIn: OrderedCollection selector: #add:.

"Debugger window shows:"
Stack:
   OrderedCollection>>add:
    SmallInteger>>/
    UndefinedObject>>DoIt

Local Variables:
  self: an OrderedCollection(1 2 3)
  newObject: 'hello'

Source Code:
  add: newObject
     lastIndex = array size ifTrue: [self makeRoomAtLast].
      lastIndex := lastIndex + 1.
      array at: lastIndex put: newObject.
      ^ newObject

[Step] [Step Into] [Step Over] [Continue] [Restart]

Implementation Hints:

Understanding thisContext:

"thisContext is the current stack frame as an object"
testMethod
    | context |
    context := thisContext.
    Transcript
        show: 'Method: ', context method selector;
        show: 'Sender: ', context sender method selector.

Walking the stack:

printStack: aContext
    | ctx |
    ctx := aContext.
    [ctx notNil] whileTrue: [
        Transcript
            show: ctx receiver class name;
            show: '>>';
            show: ctx method selector;
            cr.
        ctx := ctx sender  "Parent frame"
    ].

For stepping, you need to control process execution:

"Suspend process at breakpoint"
breakpointHit: aContext
    | process |
    process := Processor activeProcess.
    process suspend.
    self openDebuggerOn: aContext process: process.

The real Pharo debugger manipulates the Context to implement stepping. Key messages:

  • context step - execute one bytecode
  • context stepToSendOrReturn - step over
  • context resume: - continue with a value

Learning milestones:

  1. You access thisContext → You understand execution state is an object
  2. You walk the stack → You understand the sender chain
  3. You display local variables → You understand Context’s temp variables
  4. You implement stepping → You understand bytecode-level execution

Project 12: Simple Compiler (Smalltalk Subset to Bytecode)

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, Cuis-Smalltalk
  • Coolness Level: Level 5: Pure Magic (Super Cool)
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 4: Expert
  • Knowledge Area: Compilers / Parsing / Bytecode
  • Software or Tool: Pharo
  • Main Book: “Smalltalk-80: The Language and Its Implementation” (Blue Book)

What you’ll build: A compiler for a subset of Smalltalk that parses source code, builds an AST, and generates bytecode that can run on the Pharo VM—essentially, you’ll compile your own methods.

Why it teaches Smalltalk: The Blue Book literally contains the Smalltalk compiler implemented in Smalltalk. Understanding how Smalltalk compiles itself closes the loop on understanding the entire system from source code to execution.

Core challenges you’ll face:

  • Parsing Smalltalk syntax → maps to message patterns, blocks, cascades
  • Building an AST → maps to representing code as objects
  • Generating bytecode → maps to understanding the instruction set
  • Creating CompiledMethods → maps to the format the VM expects

Key Concepts:

  • Smalltalk Grammar: “Smalltalk-80: The Language” (Blue Book) Part 1
  • Bytecode Set: “Smalltalk-80: The Language” (Blue Book) Chapter 28
  • OpalCompiler: Browse the Opal compiler in Pharo
  • CompiledMethod Format: Browse CompiledMethod class

Difficulty: Expert Time estimate: 1 month+ Prerequisites: All previous projects. Strong understanding of parsing and compilers (CS theory helpful).

Real world outcome:

"Compile a simple method:"
source := 'double: x
    ^ x + x'.

| ast bytecode method |
ast := MyParser parse: source.
bytecode := MyCodeGenerator generate: ast.
method := CompiledMethod new: bytecode size.
"... install bytecode into method ..."

"Install and use it:"
Number addSelector: #double: withMethod: method.

5 double.  "Returns 10 - your compiled method runs!"

Implementation Hints:

Start with a minimal subset:

  • Unary messages only (x foo)
  • Then binary messages (x + y)
  • Then keyword messages (x at: 1 put: 2)
  • Then blocks ([:x | x + 1])

Lexer tokens:

Token subclass: #IdentifierToken.   "foo, bar, self"
Token subclass: #NumberToken.        "123, 3.14"
Token subclass: #StringToken.        "'hello'"
Token subclass: #SymbolToken.        "#foo"
Token subclass: #BinaryToken.        "+, -, *, /"
Token subclass: #KeywordToken.       "at:, put:, ifTrue:"

AST nodes:

ASTNode subclass: #LiteralNode.      "Numbers, strings"
ASTNode subclass: #VariableNode.     "self, temp vars, args"
ASTNode subclass: #MessageNode.      "Receiver + selector + args"
ASTNode subclass: #BlockNode.        "Block closure"
ASTNode subclass: #ReturnNode.       "^ expression"
ASTNode subclass: #SequenceNode.     "Statement1. Statement2."

Bytecode generation:

"Key bytecodes in Pharo:"
16-31   "Push receiver variable 0-15"
32-63   "Push temporary variable 0-31"
64-95   "Push literal constant 0-31"
96-127  "Push literal variable 0-31"
128     "Push (extended)"
176-191 "Send arithmetic message"
208-223 "Send unary message"
224-255 "Send keyword message"

Study the existing compiler:

"Compile and inspect:"
(Compiler new compile: 'foo ^ 3 + 4') inspect.
"Look at the bytecodes, literals, etc."

Learning milestones:

  1. You parse simple expressions → You understand Smalltalk’s grammar
  2. You generate bytecode for literals → You understand the instruction format
  3. You handle message sends → You understand method dispatch at bytecode level
  4. Your compiled methods run → You understand how Smalltalk really works

Project 13: Mini-VM Simulator

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, C (for comparison)
  • Coolness Level: Level 5: Pure Magic (Super Cool)
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 5: Master
  • Knowledge Area: Virtual Machines / Interpretation / Memory Management
  • Software or Tool: Pharo
  • Main Book: “Smalltalk-80: The Language and Its Implementation” (Blue Book)

What you’ll build: A simulator for the Smalltalk VM that can execute bytecode, perform message dispatch, handle primitives, and manage object memory—following the specification in the Blue Book.

Why it teaches Smalltalk: The Blue Book contains the VM specification written in Smalltalk itself. By implementing (or tracing through) this specification, you understand exactly how Smalltalk execution works, from bytecode interpretation to garbage collection.

Core challenges you’ll face:

  • Object memory model → maps to object pointers, object table
  • Bytecode interpretation → maps to fetch-decode-execute cycle
  • Message dispatch → maps to method lookup in class hierarchy
  • Primitives → maps to arithmetic, object access, I/O

Key Concepts:

  • VM Specification: “Smalltalk-80: The Language” (Blue Book) Part 4
  • Object Memory: Blue Book Chapter 26-27
  • Interpreter: Blue Book Chapter 28-29
  • SqueakVM Source: Browse the VMMaker package

Difficulty: Master Time estimate: 1-2 months Prerequisites: Project 12 completed. Deep understanding of computer architecture.

Real world outcome:

"Load bytecode and run it:"
| vm method |
vm := MiniVM new.
method := Compiler compile: 'test ^ 3 + 4'.

vm loadMethod: method.
vm run.
vm stackTop.  "Returns 7"

"Step through execution:"
vm := MiniVM new.
vm loadMethod: (Compiler compile: 'factorial: n
    n = 0 ifTrue: [^ 1].
    ^ n * (self factorial: n - 1)').
vm push: 5.  "Argument"
vm stepUntilReturn.
vm stackTop.  "Returns 120"

"Watch the execution trace:"
vm traceOn.
vm run.
"Prints:"
"  push 5"
"  send factorial:"
"    push 5, push 0, send ="
"    push false, send ifTrue:"
"    ..."

Implementation Hints:

Core VM components:

MiniVM
  instanceVariableNames:
    'memory          "Object table"
     ip              "Instruction pointer"
     sp              "Stack pointer"
     currentContext  "Active method context"
     currentMethod   "Currently executing method"'.

Main interpreter loop:

run
    [self shouldContinue] whileTrue: [
        self fetchBytecode.
        self dispatch.
    ].

dispatch
    "Based on bytecode value, call appropriate handler"
    (bytecode between: 16 and: 31) ifTrue: [^ self pushReceiverVariable].
    (bytecode between: 32 and: 63) ifTrue: [^ self pushTemporaryVariable].
    (bytecode between: 176 and: 191) ifTrue: [^ self sendArithmeticMessage].
    "etc..."

Message send implementation:

sendMessage: selector numArgs: n
    | receiver class method |
    receiver := stack at: sp - n.
    class := self classOf: receiver.
    method := self lookupMethod: selector startingAt: class.
    method ifNil: [^ self sendDoesNotUnderstand: selector to: receiver].

    (self isPrimitive: method)
        ifTrue: [self executePrimitive: method]
        ifFalse: [self activateMethod: method].

Study the real implementation:

"Load VMMaker and browse:"
Gofer it
    smalltalkhubUser: 'Pharo' project: 'VMMaker';
    package: 'VMMaker';
    load.

Learning milestones:

  1. You execute simple bytecodes → You understand the instruction set
  2. You implement message dispatch → You understand OOP at the metal
  3. You handle primitives → You understand the boundary with native code
  4. You trace execution → You understand the entire execution model

Project 14: Smalltalk-to-JavaScript Transpiler

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, PharoJS
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 4. The “Open Core” Infrastructure
  • Difficulty: Level 4: Expert
  • Knowledge Area: Transpilation / Code Generation / Language Semantics
  • Software or Tool: Pharo + PharoJS
  • Main Book: “Engineering a Compiler” by Cooper & Torczon

What you’ll build: A transpiler that converts Smalltalk code to JavaScript, handling message sends, blocks, class definitions, and Smalltalk-specific semantics like become: and thisContext.

Why it teaches Smalltalk: Transpiling forces you to understand every semantic nuance of Smalltalk. How do you represent blocks? What about non-local returns? How do you map Smalltalk’s message dispatch to JavaScript? This deep dive reveals exactly what makes Smalltalk unique.

Core challenges you’ll face:

  • Mapping Smalltalk to JS semantics → maps to classes, closures, dispatch
  • Handling non-local returns → maps to ^ inside blocks
  • Implementing message cascades → maps to ; chaining
  • Representing Smalltalk’s object model → maps to prototype vs class-based

Key Concepts:

  • PharoJS: Study the existing PharoJS project
  • Block Closures: How they differ from JS functions
  • Message Dispatch: Simulating Smalltalk dispatch in JS
  • Non-local Returns: Using exceptions for control flow

Difficulty: Expert Time estimate: 1 month+ Prerequisites: Projects 1-12 completed. JavaScript proficiency. Compiler experience.

Real world outcome:

"Smalltalk input:"
Counter >> increment
    count := count + 1.
    ^ count

Counter >> decrement
    count := count - 1.
    ^ count

"JavaScript output:"
class Counter {
    increment() {
        this.count = this.count + 1;
        return this.count;
    }

    decrement() {
        this.count = this.count - 1;
        return this.count;
    }
}

"More complex - blocks with non-local return:"
findFirst: aBlock
    self do: [:each |
        (aBlock value: each) ifTrue: [^ each]
    ].
    ^ nil

"Compiles to:"
findFirst(aBlock) {
    try {
        this._do((each) => {
            if (aBlock(each)) {
                throw new NonLocalReturn(each);  // ^ from inside block
            }
        });
        return null;
    } catch (e) {
        if (e instanceof NonLocalReturn) return e.value;
        throw e;
    }
}

Implementation Hints:

Mapping strategies:

  1. Classes: Direct mapping to ES6 classes
  2. Message sends: Method calls (requires dispatch simulation for DNU)
  3. Blocks: Arrow functions (but watch for thisContext!)
  4. Non-local returns: Use exceptions

Handling doesNotUnderstand::

class SmalltalkObject {
    _send(selector, args) {
        if (typeof this[selector] === 'function') {
            return this[selector](...args);
        }
        return this.doesNotUnderstand(new Message(selector, args));
    }
}

Handling cascades:

"Smalltalk:"
array add: 1; add: 2; add: 3.

"JavaScript:"
(() => {
    const _recv = array;
    _recv.add(1);
    _recv.add(2);
    return _recv.add(3);
})();

Study PharoJS:

"Load and explore:"
Metacello new
    baseline: 'PharoJS';
    repository: 'github://ArnoutRookworst/PharoJS';
    load.

Learning milestones:

  1. You transpile simple methods → You understand basic mapping
  2. You handle blocks correctly → You understand closure semantics
  3. You implement non-local returns → You understand control flow mapping
  4. You run Smalltalk in the browser → You’ve bridged two worlds

Project 15: Live Coding Music Synthesizer

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, SuperCollider (for audio engine)
  • Coolness Level: Level 5: Pure Magic (Super Cool)
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Audio / DSP / Live Coding
  • Software or Tool: Pharo + SuperCollider (or PortAudio)
  • Main Book: “Computer Music” by Dodge & Jerse

What you’ll build: A live coding environment for creating music where you modify synthesizer code while it’s playing, demonstrating Smalltalk’s legendary live programming capabilities in an artistic context.

Why it teaches Smalltalk: Music performance demands real-time code modification—the essence of live programming. This project shows why Smalltalk’s image-based, always-running environment is perfect for creative coding where you need to experiment in real-time.

Core challenges you’ll face:

  • Audio output → maps to interfacing with audio libraries
  • Real-time synthesis → maps to generating audio samples efficiently
  • Live code modification → maps to changing running code seamlessly
  • Timing and scheduling → maps to processes and delays

Key Concepts:

  • Digital Audio Basics: “Computer Music” by Dodge & Jerse
  • OSC Protocol: For communicating with SuperCollider
  • Processes: Smalltalk’s green threads for scheduling
  • muO (Musical Objects): Squeak’s music framework

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 1-8 completed. Basic understanding of audio/music concepts.

Real world outcome:

"Define a synth:"
mySynth := Synth new.
mySynth frequency: 440.  "A4 note"
mySynth waveform: #sine.
mySynth play.

"Hear it playing... now modify live:"
mySynth frequency: 523.  "C5 - pitch changes instantly!"
mySynth waveform: #saw.   "Timbre changes!"

"Create a sequence:"
melody := #(60 62 64 65 67 69 71 72).  "C major scale as MIDI"
[melody do: [:note |
    mySynth frequency: (440 * (2 raisedTo: (note - 69) / 12)).
    (Delay forMilliseconds: 250) wait
]] fork.

"While sequence plays, tweak:"
mySynth attack: 0.01.
mySynth decay: 0.2.
mySynth waveform: #square.

Implementation Hints:

Option 1: Use SuperCollider as audio backend:

"Send OSC messages to SuperCollider"
OSCMessage send: #'/s_new'
    args: #('default' 1000 0 1 'freq' 440)
    to: 'localhost:57110'.

Option 2: Use PortAudio bindings:

"Generate samples directly"
Synth >> nextSampleAt: time
    | phase |
    phase := time * frequency * 2 * Float pi.
    waveform = #sine ifTrue: [^ phase sin].
    waveform = #saw ifTrue: [^ (phase / Float pi) - 1].
    waveform = #square ifTrue: [^ phase sin sign].

For live coding, leverage Smalltalk’s dynamism:

"Method recompilation is instant:"
Synth >> frequency: f
    frequency := f.
    "Add vibrato on the fly by redefining this method!"

"Redefine while running:"
Synth compile: 'frequency: f
    frequency := f * (1 + (0.02 * (Time now asSeconds * 5) sin))'.
"Now all synths have vibrato!"

Learning milestones:

  1. You produce sound → You understand audio I/O
  2. You modify sound in real-time → You understand live coding
  3. You create musical sequences → You understand scheduling with processes
  4. You redefine methods while playing → You understand Smalltalk’s dynamism

Project 16: Self-Hosting Smalltalk (Bootstrap)

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Cuis-Smalltalk (cleaner for understanding)
  • Coolness Level: Level 5: Pure Magic (Super Cool)
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 5: Master
  • Knowledge Area: Bootstrapping / Metaclasses / System Building
  • Software or Tool: Pharo or Cuis
  • Main Book: “Smalltalk-80: Bits of History, Words of Advice” (Green Book)

What you’ll build: Bootstrap a minimal Smalltalk environment from scratch—create the core classes (Object, Behavior, Class, Metaclass), the basic infrastructure, and a minimal compiler, achieving a self-hosting system.

Why it teaches Smalltalk: Bootstrapping reveals the chicken-and-egg puzzle at the heart of Smalltalk: Class is an instance of its metaclass, but metaclasses are defined using Class. Understanding how this is resolved gives you the deepest possible understanding of Smalltalk’s architecture.

Core challenges you’ll face:

  • The metaclass puzzle → maps to understanding Class/Metaclass circularity
  • Minimal kernel → maps to what’s truly essential?
  • Bootstrapping sequence → maps to which objects come first?
  • Self-hosting compiler → maps to compiler that can compile itself

Key Concepts:

  • Metaclasses: “Pharo by Example” Chapter 13, “Deep into Pharo”
  • Bootstrapping: “Smalltalk-80: Bits of History” (Green Book)
  • Kernel Design: Study Cuis-Smalltalk’s minimal kernel
  • Pharo Bootstrap: https://github.com/guillep/PharoBootstrap

Difficulty: Master Time estimate: 2+ months Prerequisites: All previous projects. Deep understanding of Smalltalk internals.

Real world outcome:

"Your bootstrapped system runs:"
| system |
system := MiniSmalltalk bootstrap.

"Core classes exist:"
system at: #Object.      "The root class"
system at: #Class.       "Classes are instances of this"
system at: #Metaclass.   "Metaclass of every class"

"You can create classes:"
system evaluate: '
Object subclass: #Point
    instanceVariableNames: ''x y''.

Point >> x
    ^ x.

Point >> x: aValue
    x := aValue.
'.

"And use them:"
system evaluate: '
| p |
p := Point new.
p x: 10.
p x.  "Returns 10"
'.

Implementation Hints:

The metaclass puzzle:

Object instance-of → Object class (a Metaclass)
Object class instance-of → Metaclass
Metaclass instance-of → Metaclass class (also a Metaclass)
Metaclass class instance-of → Metaclass

Class instance-of → Class class (a Metaclass)
Class class instance-of → Metaclass

Bootstrap sequence (simplified):

  1. Create raw objects for Object, Class, Metaclass, Behavior
  2. Wire up their class pointers manually (breaking the circularity)
  3. Now you can create new classes normally
  4. Build the rest of the kernel: Boolean, True, False, Integer, Array, String, etc.
  5. Build the compiler
  6. Use the compiler to compile the compiler (self-hosting!)

Study existing bootstraps:

"Pharo's bootstrap:"
"https://github.com/guillep/PharoBootstrap"

"Cuis is simpler to understand:"
"https://github.com/Cuis-Smalltalk/Cuis-Smalltalk-Dev"

Key insight: The bootstrap initially “cheats” by hard-coding object formats, then once the metaclass machinery works, everything becomes self-consistent.

Learning milestones:

  1. You understand the metaclass hierarchy → You grasp Smalltalk’s deepest secret
  2. You create the kernel manually → You understand what’s truly fundamental
  3. You bootstrap a working image → You’ve created a Smalltalk from nothing
  4. Your compiler compiles itself → You’ve achieved self-hosting

Project Comparison Table

Project Difficulty Time Depth of Understanding Fun Factor
1. Calculator REPL Beginner Weekend ⭐⭐ ⭐⭐⭐
2. Address Book Beginner Weekend ⭐⭐⭐ ⭐⭐
3. Control Flow Demystifier Intermediate 1 week ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
4. Collection Framework Intermediate 1-2 weeks ⭐⭐⭐⭐ ⭐⭐⭐
5. Mini-SUnit Intermediate 1 week ⭐⭐⭐⭐ ⭐⭐⭐
6. Morphic Drawing App Intermediate 1-2 weeks ⭐⭐⭐ ⭐⭐⭐⭐⭐
7. Text Adventure Engine Intermediate 2 weeks ⭐⭐⭐ ⭐⭐⭐⭐⭐
8. Method Finder Advanced 1-2 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
9. HTTP Server Advanced 2 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐
10. Object Database Advanced 2-3 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐
11. Visual Debugger Advanced 2-3 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
12. Smalltalk Compiler Expert 1 month+ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
13. VM Simulator Master 1-2 months ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
14. JS Transpiler Expert 1 month+ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
15. Music Synthesizer Advanced 2-3 weeks ⭐⭐⭐ ⭐⭐⭐⭐⭐
16. Bootstrap Smalltalk Master 2+ months ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

If you’re completely new to programming:

  1. Start with Project 1 (Calculator) to get comfortable with the environment
  2. Move to Project 2 (Address Book) for basic OOP
  3. Project 3 (Control Flow) is your “aha moment” for understanding Smalltalk’s elegance
  4. Then explore Project 6 (Morphic) or Project 7 (Text Adventure) for fun

If you know other OOP languages (Java, Python, Ruby):

  1. Skim Project 1, jump to Project 3 (Control Flow) - this will blow your mind
  2. Project 4 (Collections) shows why Smalltalk’s design influenced everything
  3. Project 5 (SUnit) - you’ll see where JUnit came from
  4. Project 8 (Method Finder) demonstrates Smalltalk’s reflective power

If you want to deeply understand Smalltalk:

Follow all projects in order, but especially focus on:

  • Project 3: Understand that control structures are just messages
  • Project 11: Understand that execution contexts are objects
  • Project 12-13: Understand how Smalltalk compiles and runs
  • Project 16: Understand how Smalltalk creates itself

If you have limited time (1-2 weeks):

  1. Project 1 (1-2 days) - Get familiar
  2. Project 3 (2-3 days) - Core insight
  3. Project 7 or 15 (1 week) - Something fun and practical

Final Capstone Project: Complete Development Environment

Following the same pattern as above, here is the ultimate project that applies all concepts:

  • File: LEARN_SMALLTALK_DEEP_DIVE.md
  • Main Programming Language: Pharo Smalltalk
  • Alternative Programming Languages: Squeak, Cuis-Smalltalk
  • Coolness Level: Level 5: Pure Magic (Super Cool)
  • Business Potential: 4. The “Open Core” Infrastructure
  • Difficulty: Level 5: Master
  • Knowledge Area: IDE / Language Design / System Architecture
  • Software or Tool: Pharo
  • Main Book: “Smalltalk-80: The Language and Its Implementation” (Blue Book)

What you’ll build: A complete mini-IDE for a Smalltalk dialect: a class browser, workspace, debugger, inspector, and package manager—essentially recreating the core Smalltalk development experience from scratch.

Why it teaches everything: This project integrates every concept: GUI (Morphic), reflection (browsing code), the execution model (debugging), persistence (saving code), parsing (syntax highlighting), and the metaclass system (creating classes). It’s the ultimate proof of Smalltalk mastery.

Core challenges you’ll face:

  • Class Browser → Browse classes, methods, categories; edit and recompile
  • Workspace → Execute arbitrary code, syntax highlighting
  • Debugger → Step through code, inspect contexts, hot-fix bugs
  • Inspector → Examine any object’s structure
  • Package Manager → Load/save code to files, dependency management

Real world outcome: A working IDE that can:

┌─────────────────────────────────────────────────────┐
│ MyBrowser                                      [x]  │
├───────────────┬─────────────────────────────────────┤
│ Packages      │ Classes        │ Methods           │
│ ─────────     │ ───────        │ ───────           │
│ ▼ Collections │ OrderedCollec. │ add:              │
│   Kernel      │ Array          │ addFirst:         │
│   Morphic     │ Set            │ remove:           │
│               │ Dictionary     │ do:               │
├───────────────┴─────────────────────────────────────┤
│ add: anObject                                       │
│     lastIndex = array size ifTrue: [                │
│         self makeRoomAtLast].                       │
│     lastIndex := lastIndex + 1.                     │
│     array at: lastIndex put: anObject.              │
│     ^ anObject                                      │
└─────────────────────────────────────────────────────┘

Implementation Hints:

Architecture:

MyIDE
├── MyClassBrowser (Morphic window)
│   ├── PackageList, ClassList, MethodList (ListMorphs)
│   └── CodePane (TextMorph with syntax highlighting)
├── MyWorkspace (evaluate arbitrary code)
├── MyInspector (view object internals)
├── MyDebugger (step through execution)
└── MyPackageManager (load/save code)

For the Class Browser:

"Get all packages"
RPackageOrganizer default packages.

"Get classes in a package"
aPackage definedClasses.

"Get methods"
aClass methods.
aClass class methods.  "Class-side methods"

"Get source"
aMethod sourceCode.

"Recompile"
aClass compile: newSource.

For the Debugger, use thisContext and process suspension as explored in Project 11.

For syntax highlighting, parse the source and color tokens appropriately.

Time estimate: 2-3 months Prerequisites: All 16 projects completed

Learning milestones:

  1. Browser works → You understand the full class/method structure
  2. Workspace executes code → You understand compilation and evaluation
  3. Debugger steps through code → You understand execution contexts
  4. Inspector examines objects → You understand object internals
  5. Everything integrates → You’ve mastered Smalltalk

Essential Resources

Free Books (prioritized)

  1. Pharo by Example - Start here, covers all basics
  2. Deep into Pharo - Advanced topics
  3. Smalltalk-80: The Language (Blue Book) - The original specification
  4. Squeak by Example - Alternative intro
  5. Dynamic Web Development with Seaside - Web development

Online Resources

Community

Implementations to Explore


Summary

# Project Main Programming Language
1 Interactive Calculator REPL Pharo Smalltalk
2 Personal Address Book with Persistence Pharo Smalltalk
3 Control Flow Demystifier Pharo Smalltalk
4 Collection Framework Explorer Pharo Smalltalk
5 Unit Testing Framework (Mini-SUnit) Pharo Smalltalk
6 Morphic Drawing Application Pharo Smalltalk
7 Text Adventure Game Engine Pharo Smalltalk
8 Method Finder and Code Browser Pharo Smalltalk
9 Simple HTTP Server Pharo Smalltalk
10 Object Database (OODB) Pharo Smalltalk
11 Visual Debugger Pharo Smalltalk
12 Simple Compiler (Smalltalk Subset to Bytecode) Pharo Smalltalk
13 Mini-VM Simulator Pharo Smalltalk
14 Smalltalk-to-JavaScript Transpiler Pharo Smalltalk
15 Live Coding Music Synthesizer Pharo Smalltalk
16 Self-Hosting Smalltalk (Bootstrap) Pharo Smalltalk
Final Complete Development Environment Pharo Smalltalk

Happy Smalltalking! Remember: in Smalltalk, you don’t just write code—you have a conversation with live objects. Enjoy the journey! 🎩