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 Symbol and a collection holds Objects, 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)
  • ❌ nil is an object, not an absent type: A send to nil raises MessageNotUnderstood rather 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, and size are all sends, so a call site does not distinguish an operator from a method
  • ✅ The class object holds the constructor and its initialize hook: new and new: 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: or ifCurtailed: 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 Error subclass, 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: and ifCurtailed: separate the normal path from the cleanup path: The cleanup runs however the block exits
  • ✅ MessageNotUnderstood is a defined signal: A send that no method implements raises it with the receiver and the selector
  • ✅ resume:, return:, and retry state 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

  • ✅ Process and Semaphore are part of the class library: A scheduler multiplexes processes, and a semaphore is the synchronisation primitive
  • ✅ Monitor and SharedQueue are in the class library: The common coordination patterns ship with the environment
  • ✅ Delay suspends 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.st needs 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, and Seaside are 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 nil are 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
  • ✅ Metacello and Monticello give 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 MessageNotUnderstood opens 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: 2 sends both messages to a
  • ✅ 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: e parses 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)
  • ✅ TestCase and 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?

  • ⚠️ Runs on a virtual machine, above native code in the energy and time benchmarks [5]; a causal re-analysis attributes most of the gap to execution time [6].

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:

All published papers

References

[1]
Y. Loth, “On the nature of coupling: All coupling as shared resource access.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925122.
[2]
Y. Loth, “Coupling as a consequence of cohesion: Decomposition, the coupling floor, and domain-imposed coupling.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925201.
[3]
Y. Loth, “On the nature of cohesion: Cohesion as correctness relative to a partitioning rule.” Zenodo, Jul. 2026. doi: 10.5281/zenodo.21703193.
[4]
InterNational Committee for Information Technology Standards, “Programming language Smalltalk.” ANSI; ANSI INCITS 319-1998, Washington, DC, 1998.
[5]
R. Pereira et al., “Energy efficiency across programming languages: How do energy, time, and memory relate?” in Proceedings of the 10th ACM SIGPLAN international conference on software language engineering (SLE), 2017, pp. 256–267. doi: 10.1145/3136014.3136031.
[6]
N. van Kempen, H.-J. Kwon, D. T. Nguyen, and E. D. Berger, “It’s not easy being green: On the energy efficiency of programming languages,” in Proceedings of the 40th IEEE/ACM international conference on automated software engineering (ASE), 2025. doi: 10.1109/ASE63991.2025.00131.
[7]
S. Joel, J. J. Wu, and F. H. Fard, “A survey on LLM-based code generation for low-resource and domain-specific programming languages,” ACM Transactions on Software Engineering and Methodology, 2025, doi: 10.1145/3770084.