Smalltalk (ANSI Smalltalk)
This assessment applies the coupling account — coupling as access to a shared resource, and the coupling floor [1], [2] — and the knowledge-embodiment theorem [3]; the language is assessed against its official reference [4].
1 Assessment
This page assesses Smalltalk in five parts: core coupling — what the language’s own constructs force; ecosystem coupling — what the surrounding platform, tooling, and community cost; engineering readiness — whether the artifact can be reproduced, verified, observed, worked in quickly, connected to other languages, and governed openly; the profile — the unscored facts a score cannot express; and the scoring detail — the mechanism marks and the arithmetic behind every score. A ✅ bullet marks a construct that helps. A ⚠️ or ❌ bullet marks a cost; the scoring model does not price every cost named here.
Core coupling 47/100 · Ecosystem coupling 72/100 · Engineering readiness 57/100
1.1 Core coupling
1.1.1 Type
- ❌ No static type check: A variable has no declared type, and a message send is resolved when it executes, so a mismatch is reported at run time (T1)
- ❌ A receiver’s type is a protocol, not a class: Any object that answers a selector is acceptable, and the compiler checks no such promise (T3)
- ❌ No sum types and no generics: An alternative is a class hierarchy or a
Symboland a collection holdsObjects, so both the branch and the element type are conventions (T4, T5) - ❌ No namespaces: A class is entered in the one global system dictionary, so a class name collision is a system-wide event (T6)
- ❌
nilis an object, not an absent type: A send tonilraisesMessageNotUnderstoodrather than failing a static check (T10) - ✅ Message passing separates a request from its implementation: A send names a selector, and the receiver chooses the method that runs
1.1.2 Construction
- ❌ Global mutable state is the default: A class variable, a class instance variable, or a
Smalltalk at:global is reachable from every method, so construction couples to shared state (C1) - ❌ Objects are mutable by default: An accessor writes a field in place, and nothing in the language makes an object immutable (C2)
- ✅ Every operation is a message:
+,new, andsizeare all sends, so a call site does not distinguish an operator from a method - ✅ The class object holds the constructor and its
initializehook:newandnew:are class-side methods and a subclass can override the setup that follows - ⚠️ The class browser compiles into the running system: A change is made in the live image, and the deployed artifact is the image rather than the source files
- ⚠️ Open classes allow a change from anywhere: Any tool or package can add a method to
Object, so a change has system-wide reach
1.1.3 Memory
- ❌ A garbage collector reclaims memory: Collection runs on the virtual machine’s schedule, so the program cannot choose when a pause happens (M2)
- ❌ No RAII: A resource is released by an explicit message, though
ensure:orifCurtailed:runs a release block on the way out (M3) - ❌ No ownership model: A reference is an untracked alias, and only the virtual machine knows when an object is unreachable (M4)
- ✅ No manual deallocation: An object becomes garbage, and no free message exists
- ✅
ensure:binds a release to a protected block: The block runs on a normal return and on a signal that unwinds through it - ✅ Collection is invisible in the source: An instantiation is
new, and no deallocation is ever written
1.1.4 Error
- ❌ Failures are signals and no selector describes them: A send may raise any
Errorsubclass, so the failure set is not readable from the method name (Er2) - ✅
on:do:installs a structured handler: A handler is bound to a class of signal for the duration of a block, and it returns a value to the sender - ✅
ensure:andifCurtailed:separate the normal path from the cleanup path: The cleanup runs however the block exits - ✅
MessageNotUnderstoodis a defined signal: A send that no method implements raises it with the receiver and the selector - ✅
resume:,return:, andretrystate the recovery: A handler can resume the signal, return a value, or re-run the block - ⚠️ No handler is checked to exist: The compiler does not know which signals a send raises, so the handler is written from documentation
1.1.5 Concurrency
- ✅
ProcessandSemaphoreare part of the class library: A scheduler multiplexes processes, and a semaphore is the synchronisation primitive - ✅
MonitorandSharedQueueare in the class library: The common coordination patterns ship with the environment - ✅
Delaysuspends a process for a stated time: The scheduler resumes it, so a timed wait is a library call rather than a system call - ⚠️ Every object is reachable from every process: The class library supplies the synchronisation primitives, and the discipline that keeps two processes apart from one shared object is the programmer’s
- ⚠️ The classic virtual machine decides when a process runs: Priority decides which process resumes, and the switch points belong to the interpreter, so a process at the same priority can hold the CPU through a tight loop
1.1.6 Platform
- ✅ The language is standardised independently of a vendor: ANSI INCITS 319-1998 fixes the syntax and the protocols of the standard class library
- ✅ Several independent implementations exist: Squeak, Pharo, VisualWorks, GNU Smalltalk, and GemStone/S each run the language
- ✅ GNU Smalltalk runs a text file as an executable:
gst file.stneeds no image, so a program can be deployed as source - ⚠️ The deployment unit is a virtual-machine image: An image carries the classes, the methods, and the live state, so a deployment couples to one virtual machine
- ⚠️ The image format is not standardised: A program moves between dialects through source, not through an image file
- ⚠️ Portability stops at the dialect’s tools:
Monticello,Metacello, andSeasideare dialect packages, so a portable program stays within the ANSI subset
1.1.7 Trade-offs (not coupling)
- The image is a deliberate development model: A running system is editable and the image is the artifact, which is the point of the environment
- Live coding is a workflow choice: A change is compiled into the running system; the compiler is the mechanism, and the workflow is the choice
- Open classes are deliberate flexibility: A method can be added to any class, and the global reach of that change is the price
- The absent static typing is a design choice: No annotation is required, so the discipline is the programmer’s
- Everything is an object by design: Classes, blocks, and
nilare objects, and the uniformity is the language’s identity - Niche adoption is an outcome: The language predates and influenced the mainstream object-oriented languages
1.2 Ecosystem coupling
1.2.1 Performance
- ✅ Modern dialects compile the hot path: The Cog virtual machine of Pharo and Squeak compiles a method to machine code once it becomes hot
- ✅ The virtual machine is the optimisation boundary: The runtime optimises a method, so no source-level tuning is needed to reach the native path
- ✅ In-line caches make a polymorphic send cheap: A send site caches the receiver’s class and its method, so a repeated send costs one lookup
- ⚠️ Steady-state speed is below a statically compiled language: A dynamic send is a polymorphic call site, so a numeric inner loop is slower than in Ada or C
- ⚠️ Warm-up precedes peak speed: The interpreter runs a method until the runtime decides to compile it
1.2.2 Ecosystem
- ❌ The ecosystem is small and centred on a few communities: Pharo and Squeak carry most of the active library work, and commercial Smalltalk is a niche (Ec3)
- ✅ Pharo and Squeak are active open-source communities: Each ships a maintained image with tools, a package manager, and libraries
- ✅ The commercial implementations are long-lived: VisualWorks, VA Smalltalk, and GemStone/S have decades of use
- ✅
MetacelloandMonticellogive package management and versioning: A package is loaded from a repository with a version map - ✅ Seaside and Glamorous Toolkit add depth: A continuation-based web framework and a moldable development environment are both maintained
- ⚠️ A library is often image- or dialect-specific: A package loaded into one dialect usually needs adaptation for another
1.2.3 Evolution
- ✅ Pharo releases on a regular cadence with a migration guide: A release names the changes to the image and the tools
- ✅ A file-in of source is the migration unit across dialects: A program’s classes and methods load into another image
- ✅ The ANSI standard is a stable foundation: The standardised language does not move under a program that stays within it
- ⚠️ Later work is dialect-specific: Each community extends the class library and the tools on its own, so the dialects diverge
- ⚠️ A dialect fork is the usual form of a break: Squeak and Pharo diverged from a shared image, so a program may target one community only
- ⚠️ The image carries its own history: An old image can hold classes and methods that the source no longer describes, so a migration needs care
1.2.4 Domain
- ✅ Excellent for prototyping and live systems: The image, the class browser, and the debugger let a system be built and changed while it runs
- ✅ A strong fit for exploratory modelling: The uniform object model and the interactive debugger support a step-by-step design
- ✅ A web application has a framework: Seaside serves a deployed web application with continuations, and GemStone/S adds a transactional object store
- ✅ The environment is the development tool: A refactoring browser, a debugger, and a test runner are part of the image
- ⚠️ Commercial product work is rare today: A new product team usually picks another language, so precedents and hiring are scarce
1.2.5 Learning
- ❌ The image-based workflow is unfamiliar and steep: A program is edited inside a running system, and a newcomer must learn the browser, the debugger, and the image at once (L1)
- ❌ The uniform message model must be internalised: Control flow is a message (
ifTrue:,whileTrue:) and class creation is a message, which is unlike an expression-based language - ✅ The debugger teaches by inspection: A
MessageNotUnderstoodopens on the failing frame with the receiver and the selector in view - ✅ The browser shows the whole system: A method, its senders, and its implementors are reachable from one tool
- ⚠️ Little feedback arrives before run time: With no static check, a mistake appears when the path executes
1.2.6 Syntax
- ✅ A message send has three small forms: Unary (
size), binary (+), and keyword (at:put:), and all three are sends - ✅ Blocks give inline control flow:
[:x | x + 1]is a closure in the same syntax as everywhere else - ✅ Cascades group several sends to one receiver:
a add: 1; add: 2sends both messages toa - ✅ The grammar is small: The language is famously described in a few lines, and the class library carries the rest
- ⚠️ A keyword message can nest confusingly:
a b: c d: eparses by fixed rules, so a reader must recall them
1.2.7 Testing
- ❌ A selector or type error appears only when the line runs: With no static check, the first execution of a path is the first check of it (G3)
- ✅
TestCaseand the SUnit runner ship with the environment: A test class with test-prefixed methods is run by the test runner - ✅ The debugger opens on the failing test frame: A failure can be inspected and fixed in the running system
- ✅
should:raise:asserts on a signal: A test states the error class it expects - ✅ The test suite lives in the image with the code: A test is a class beside the class it tests
- ⚠️ An unexecuted path is untested and unchecked: A test covers the branches it runs, and a dynamic mistake in another branch waits
1.2.8 Security
- ❌ The absent static typing is a security-relevant gap: A selector error, a wrong argument, or a type confusion is detected at run time, and an unexecuted path is not checked (Q2)
- ✅ Memory safety is the virtual machine’s responsibility: The machine manages references, so a buffer overflow or a use-after-free is not expressible in Smalltalk source
- ✅ The standard class library exposes no address: There is no pointer value in the language for a program to misuse
- ⚠️ The image persists running state to a file: An image is a serialised object graph, so a value that should not be saved can be written into the deployed artifact
- ⚠️
perform:reaches a method by a computed selector: A selector built from input invokes code the source does not name - ⚠️
Compiler evaluate:compiles a string into the running system: An untrusted string must never reach it
1.3 Engineering readiness
1.3.1 Reproducibility
- ❌ Builds are not byte-reproducible by default.
- ❌ Dependencies resolve without a lock by default.
1.3.2 Supply chain
- ⚠️ Published packages carry no signed provenance by default.
- ⚠️ The package registry is a flat name space.
1.3.3 Observability
- ⚠️ No stable tracing and metrics standard.
- ⚠️ The standard library has no structured logging.
1.3.4 Feedback
- ✅ No charged mechanism in this dimension.
1.3.5 Interop
- ⚠️ No maintained portable target.
1.3.6 Governance
- ✅ No charged mechanism in this dimension.
1.4 Profile
Unscored, and kept separate from every score on purpose. The profile records what a mechanism price cannot express: what a running program costs, how well a coding model and an agent work in the language, and whether a safety-critical path exists.
1.4.1 Energy and cost
What this axis asks: what does a running program cost in energy and time?
1.4.2 AI and agent readiness
What this axis asks: how well does a coding model and an agent work in this language?
- ❌ Sparse training data: models write it unreliably [7].
1.4.3 Safety certification
What this axis asks: is there a certified or standards-qualified path to a safety-critical system?
- ⚠️ No certified or standards-qualified path for safety-critical systems.
1.5 Scoring detail
1.5.1 Core coupling Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Type | T1 |
dynamic no static check | ✅ | −15 |
T2 |
weak static | — | 0 | |
T3 |
dynamic/duck dispatch | ✅ | −8 | |
T4 |
no sum types | ✅ | −4 | |
T5 |
no generics | ✅ | −4 | |
T6 |
no namespaces | ✅ | −8 | |
T7 |
type erasure/reflection | — | 0 | |
T8 |
operator overloading | — | 0 | |
T9 |
inheritance required for polymorphism | — | 0 | |
T10 |
no null-safety | ✅ | −8 | |
T11 |
no module boundary | ✅ | −8 | |
T12 |
no effect tracking | ✅ | −8 | |
T13 |
async/sync colouring | — | 0 | |
T14 |
unchecked foreign boundary | ✅ | −4 | |
| Type subtotal | −15 (−67) | |||
| Construction | C1 |
global mutable state by default | ✅ | −15 |
C2 |
no immutability by default | ✅ | −4 | |
C3 |
constructors coupled to allocation | — | 0 | |
C4 |
no constructor/validation | — | 0 | |
| Construction subtotal | −15 (−19) | |||
| Memory | M1 |
manual deallocation | — | 0 |
M2 |
GC non-deterministic pauses | ✅ | −8 | |
M3 |
no RAII | ✅ | −4 | |
M4 |
no ownership model | ✅ | −8 | |
| Memory subtotal | −15 (−20) | |||
| Error | Er1 |
no structured path | — | 0 |
Er2 |
unchecked exceptions only | ✅ | −8 | |
Er3 |
errors ignored/non-exhaustive | — | 0 | |
Er4 |
no composable result type | — | 0 | |
| Error subtotal | −8 | |||
| Concurrency | K1 |
GIL/no parallel | — | 0 |
K2 |
shared mutable state default | — | 0 | |
K3 |
manual sync no race detection | — | 0 | |
K4 |
no structured concurrency | — | 0 | |
K5 |
no primitives | — | 0 | |
| Concurrency subtotal | 0 | |||
| Platform | P1 |
cannot function outside one vendor/domain | — | 0 |
P2 |
single-vendor but other uses | — | 0 | |
P3 |
legacy/discontinued platform | — | 0 | |
| Platform subtotal | 0 |
Core Dimensions score: 47/100 — total cost = −53 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
1.5.2 Ecosystem coupling Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Performance | R1 |
interpreted no AOT/JIT | — | 0 |
R2 |
GC pauses | — | 0 | |
R3 |
JIT warmup | — | 0 | |
| Performance subtotal | 0 | |||
| Ecosystem | Ec1 |
no package manager | — | 0 |
Ec2 |
fragmented tooling | — | 0 | |
Ec3 |
small/niche/discontinued | ✅ | −8 | |
Ec4 |
proprietary | — | 0 | |
Ec5 |
platform-locked tooling | — | 0 | |
Ec6 |
monoculture | — | 0 | |
Ec7 |
legacy burden | — | 0 | |
| Ecosystem subtotal | −8 | |||
| Evolution | V1 |
breaking changes | — | 0 |
V2 |
frozen | — | 0 | |
V3 |
discontinued | — | 0 | |
V4 |
glacial standards | — | 0 | |
| Evolution subtotal | 0 | |||
| Domain | D1 |
domain/vendor lock-in | — | 0 |
D2 |
poor domain fit | — | 0 | |
D3 |
framework forces architecture | — | 0 | |
| Domain subtotal | 0 | |||
| Learning | L1 |
steep | ✅ | −8 |
L2 |
large surface/multi-paradigm | — | 0 | |
L3 |
sparse docs | — | 0 | |
| Learning subtotal | −8 | |||
| Syntax | S1 |
verbose | — | 0 |
S2 |
inconsistent | — | 0 | |
S3 |
punctuation-heavy | — | 0 | |
| Syntax subtotal | 0 | |||
| Testing | G1 |
weak tooling | — | 0 |
G2 |
poor debugging | — | 0 | |
G3 |
runtime-only errors | ✅ | −4 | |
| Testing subtotal | −4 | |||
| Security | Q1 |
memory-unsafe | — | 0 |
Q2 |
dynamic/weak typing risk | ✅ | −8 | |
Q3 |
known vulnerability/injection | — | 0 | |
| Security subtotal | −8 |
Meta Dimensions score: 72/100 — total cost = −28 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
1.5.3 Engineering readiness Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Reproducibility | Y1 |
build not reproducible | ✅ | −8 |
Y2 |
dependencies not pinned | ✅ | −8 | |
| Reproducibility subtotal | −15 (−16) | |||
| Supply chain | Y3 |
no signed provenance | ✅ | −8 |
Y4 |
registry without namespacing | ✅ | −4 | |
| Supply chain subtotal | −12 | |||
| Observability | Y5 |
no stable tracing standard | ✅ | −8 |
Y6 |
no structured logging | ✅ | −4 | |
| Observability subtotal | −12 | |||
| Feedback | Y7 |
slow build | — | 0 |
Y8 |
no fast in-editor analysis | — | 0 | |
| Feedback subtotal | 0 | |||
| Interop | Y9 |
no foreign-function interface | — | 0 |
Y10 |
no portable target | ✅ | −4 | |
| Interop subtotal | −4 | |||
| Governance | Y11 |
single-vendor governance | — | 0 |
Y12 |
closed specification | — | 0 | |
| Governance subtotal | 0 |
Readiness Dimensions score: 57/100 — total cost = −43 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
Verdict: Smalltalk’s uniform object model keeps one kind of coupling low — a send names a selector, so a caller is not bound to the receiver’s implementation, and blocks, on:do:, and ensure: give structured behaviour and recovery. The core score is nevertheless in the middle band, because the language’s own constructs force coupling elsewhere: no static type check, no namespaces, no module boundary, no ownership model, global mutable state by default, and open classes that let any tool change any class. The platform cost adds a small ecosystem, an image-based deployment that couples to one virtual machine, a run-time-only error model, and a steep learning curve. Main strength: live, image-based development with a best-in-class debugger, and the language’s proven influence on later object-oriented languages.
The formal development. The definitions, theorems, and proofs behind this page are published in:
- On the Nature of Coupling: All Coupling as Shared Resource Access — Zenodo, 2026 — doi:10.5281/zenodo.21925122
- Coupling as a Consequence of Cohesion: Decomposition, the Coupling Floor, and Domain-Imposed Coupling — Zenodo, 2026 — doi:10.5281/zenodo.21925201
- On the Nature of Cohesion: Cohesion as Correctness Relative to a Partitioning Rule — Zenodo, 2026 — doi:10.5281/zenodo.21703193