Blog › ICP guides

Squeak developer on retainer: BlockClosure arity, WrongNumberOfArguments, Morphic system, and Squeak Smalltalk on monthly retainer

October 2, 2026 · ~14 min read

A Squeak developer was building an educational molecular dynamics simulation on Squeak 6.0. The simulation modeled particle motion using a collection of update blocks — one block per force type — each of which was responsible for applying a force component to a particle over a timestep. The update blocks were defined with two arguments:

updateBlock := [:particle :dt | particle position: particle position + (particle velocity * dt)].

The simulation engine iterated over the particle collection using do: and applied each update block inside the loop. The developer wrote the loop as:

particles do: [:p | updateBlock value: p].

value: sends exactly one argument to a BlockClosure. The block updateBlock was defined to take two arguments (:particle and :dt). In Squeak, sending a block a message with the wrong number of arguments raises WrongNumberOfArguments: This block requires exactly 2 arguments at the point of the value: call — not at the block definition. The developer had a unit test that correctly called updateBlock value: testParticle value: 0.016 and it passed. The production simulation loop used the one-argument value: form and failed on the first simulation tick. Three simulation runs terminated with the same WrongNumberOfArguments error before the diagnosis. The fix was changing the loop to particles do: [:p | updateBlock value: p value: timeStep], passing both the particle and the timestep. Wrong simulation runs: 3 → 0. The work log said “fixed block call in simulation loop, 3h.” What is invisible is that Squeak’s value: family sends exactly one, two, three, or N arguments to a block — each variant is a distinct message; the block’s expected arity is declared at definition time; the mismatch only surfaces at the call site at runtime; and the test that covered the correct call never tested the production iteration path.

Squeak Smalltalk overview: the message-passing environment since 1995

Squeak originated at Apple in 1995, developed by Alan Kay, Dan Ingalls, Ted Kaehler, John Maloney, and Scott Wallace — the team that had previously worked on Smalltalk-80 at Xerox PARC. The goal was a fully portable, open-source Smalltalk implementation whose entire implementation could be written in Smalltalk itself (the “meta-circular” design). After Alan Kay moved to Disney, Squeak was used as the foundation for Etoys, a tile-based programming environment for children that influenced MIT Scratch. Squeak became fully open source in 2006 under the Apache License 2.0. In 2008, a group of Squeak contributors forked the project as Pharo, pursuing a cleaner class library and more active tooling; Pharo and Squeak have since diverged significantly in library organization, UI framework, and community direction. Current Squeak is at version 6.0 (released 2022, with incremental releases since); current Pharo is at version 12 (2024).

The design premise of Squeak is that everything is an object and all computation is message passing. Numbers are objects that respond to arithmetic messages; 3 + 4 is the binary message + sent to the object 3 with argument 4. Classes are objects (instances of their metaclass). Methods are objects (instances of CompiledMethod). Blocks are objects (instances of BlockClosure). This uniformity means the runtime is inspectable at every level: an object can be opened in an Inspector, its class can be browsed in the System Browser, its method compilations can be examined as bytecode, and the current execution context can be inspected via thisContext. The Squeak debugger is not a separate tool but a Morphic window that opens when an unhandled exception reaches the top of the call stack, allowing the developer to inspect and modify the live environment.

Squeak’s image-based persistence is a defining characteristic. The entire Squeak environment — all classes, all objects, all method definitions, all open windows, all running processes — is serialized to a single .image file (paired with a .changes file that records every source edit since the last image save). Saving the image captures a complete snapshot of the runtime. Opening the image resumes execution from where it was saved. This means Squeak retainer work involves managing images as deployment artifacts: a “production image” may be a specific .image file that is loaded by a server process or a kiosk. Changes to the production image require either modifying the running image and saving it, or loading changes from .st or .mcz (Monticello package) files into the running image. Image corruption — a save that was interrupted, an image that was modified by incompatible packages, or an image that fails to restore startup state — is a real operational risk in deployed Squeak systems.

Squeak retainer work today covers: educational simulation and game platform maintenance (Squeak and Pharo are used in university computer science courses and research projects in education technology; maintaining simulation engines, physics environments, and visual programming tools built on Morphic); image and package management (tracking which Monticello packages are loaded, reproducing a stable image from source, migrating images across Squeak major versions); legacy application maintenance (Squeak was used in research and industrial applications through the 2000s; some of these systems remain in production on specific .image files that must be maintained without breaking compatibility); and new development on Pharo for data analytics, web services (Seaside, Teapot), and domain-specific applications.

BlockClosure arity: value, value:, value:value:, and valueWithArguments:

A BlockClosure in Squeak is an object that encapsulates a sequence of Smalltalk expressions together with a reference to the enclosing lexical scope (the activation context in which the block was created). Blocks are created using bracket notation: [ expressions ]. A block with no arguments is evaluated by sending it the value message: [ 3 + 4 ] value returns 7. A block with one argument is declared as [:x | expressions] and is evaluated by sending value: with one argument: [:x | x * 2] value: 5 returns 10. A block with two arguments uses [:x :y | expressions] and is evaluated with value:value:: [:x :y | x + y] value: 3 value: 4 returns 7. The Squeak class library defines value, value:, value:value:, and value:value:value: as distinct messages. For blocks with four or more arguments, valueWithArguments: anArray passes arguments as an Array.

Sending a block the wrong arity message raises WrongNumberOfArguments: sending value to a one-argument block, sending value: to a zero-argument block, sending value: to a two-argument block. The error fires at the message send, not at the block definition. This timing is important for diagnosis: the error occurs in the iteration code (or any code that calls the block), not in the method where the block was created. A common pattern that exposes this: a block is defined in one method with two parameters, stored in an instance variable, and later called from a different method that was written expecting a one-parameter block. The bug is invisible until the call site executes. The systematic diagnostic is to look at the block definition to count the argument declarations (:name entries before the | in the block) and compare to the message used at the call site.

A related arity issue: using do: versus doWithIndex: (or withIndex:do: in Pharo). OrderedCollection>>do: evaluates its block with one argument (each element). OrderedCollection>>doWithIndex: evaluates its block with two arguments (element, index). A developer who writes a two-argument block and passes it to do: gets a WrongNumberOfArguments error on the first iteration. The symmetric error: a developer who writes doWithIndex: with a one-argument block gets the index silently discarded in some Smalltalk implementations, or a WrongNumberOfArguments error in strict ones. In Squeak, the block arity is checked at the do: call site against the number of arguments the iteration protocol will pass. Another variant: collect:, select:, detect:, and inject:into: all pass different numbers of arguments to their blocks; inject:into: passes two arguments (the accumulator and each element) and requires a two-argument block.

The valueWithArguments: anArray message provides the escape hatch for dynamic arity. If the number of arguments to pass to a block is not known at compile time, block valueWithArguments: argArray is the correct form. The array size must match the block’s declared arity. This pattern appears in callback registries, event dispatch systems, and plugin architectures where a block is stored alongside a parameter list.

Morphic, the class hierarchy, and thisContext

Morphic is the direct manipulation graphics framework built into Squeak. Every visual object is a Morph — a Smalltalk object that knows how to draw itself, accept mouse and keyboard events, and participate in a layout hierarchy. The root of every Squeak display is the World (a PasteUpMorph), which contains submorphs that contain submorphs recursively. Each morph has a bounds rectangle, a position, and a color, and can be decorated with borders, shadows, and balloon help. Morphic is not a retained-mode scene graph: each morph is responsible for repainting itself when it changes. The correct protocol for signaling that a morph needs to repaint is to send changed to the morph, which marks the morph’s bounds as damaged in the display damage list and schedules a repaint. The Squeak display process processes the damage list each frame and sends drawOn: aCanvas to each damaged morph.

The common Morphic retainer bug: a developer modifies a morph’s state (changing a color, updating a displayed value, toggling a visual property) but does not send changed. The morph’s internal state is correct, but the display is stale because no damage was recorded. The visual does not update until the morph is covered by another window and uncovered (which forces a full repaint) or until the user triggers an incidental repaint. Diagnosis: open the morph in the Inspector, verify that its instance variables reflect the new state, then check whether the damage list is empty. Fix: add self changed after the state modification. The inverse bug: sending changed too aggressively (on every tick of a step morph) when only a bounding rect subset changed — use self invalidRect: changedBounds to limit repaint to the changed region.

thisContext is a pseudo-variable in Squeak that refers to the current method activation context. Sending thisContext sender returns the context of the calling method; thisContext method selector returns the selector of the current method. This enables Squeak code to inspect and navigate the live call stack without a debugger breakpoint. Retainer use cases: a logging framework that records the calling method name without a stack trace exception; a test framework that identifies which test is currently running; a debugging utility that prints the call chain when an unexpected value is encountered. The key constraint: thisContext is only valid during live execution; storing a context reference and examining it after the method has returned gives a reference to a terminated context whose stack frame is no longer live. The Squeak VM may garbage-collect the context object once its method returns unless the context is explicitly retained.

Typical Squeak retainer work and what it looks like in a work log

BlockClosure arity diagnosis is the largest category of Squeak retainer work that produces no visible artifact. The pattern: a block is defined in a callback-registration method, stored in a variable or passed to a collection, and called from a separate iteration method. The definition site and the call site are in different contexts; the developer who wrote the call site may have written it months or years after the definition. A refactor that changes a block from zero to one argument (to make it testable with an explicit input) requires updating all call sites from value to value: input. A refactor that adds a second argument to support a new parameter requires updating all call sites from value: x to value: x value: y or restructuring the block as a method. Work log entry: “Simulation engine: updateBlock value: particle raised WrongNumberOfArguments; block definition: [:particle :dt | ...] requires 2 arguments; call site used value: (1 argument); fix: changed to value: p value: timeStep; wrong simulation runs before: 3; after: 0; 3h.”

Image and package management is the second category. Squeak images are brittle across version boundaries: a method that relies on internal API from Squeak 5.x may not exist in Squeak 6.0, and the failure surfaces as a doesNotUnderstand: at startup or first use rather than at load time. Retainer issues: an image that loads correctly on the developer’s machine fails on the production kiosk because the production image is a different version; a Monticello package that loads correctly into a clean image fails to load into an existing image because of conflicting class definitions; a startup action that modifies global state prevents the image from entering a clean state after restoration. Work log entry: “Production kiosk image: startup action SimulationWorld start called World openInHand which is not available in Squeak 6.0 (was deprecated in 5.3); doesNotUnderstand: #openInHand on startup; fix: replaced with World addMorphBack: simulationMorph; startup failures before: every boot; after: 0; 6h.”

Class hierarchy maintenance is the third category. Squeak class queries are sometimes confused between subclasses (direct subclasses only), allSubclasses (full transitive closure of the subclass hierarchy), and allSuperclasses (all ancestors up to Object and ProtoObject). A plugin discovery system that uses PluginBase subclasses finds only the direct subclasses registered immediately under PluginBase — it misses plugins that are subclasses of a direct subclass. Fix: use PluginBase allSubclasses. Work log entry: “Plugin discovery: PluginBase subclasses returned 3 plugins; system has 12 plugins in 3 inheritance levels; fix: changed to PluginBase allSubclasses; undiscovered plugins before: 9; after: 0; 2h.”

Track Squeak developer retainer hours without the status emails

When a 3-hour session traces WrongNumberOfArguments to a simulation loop that called value: on a two-argument block — because the block had been refactored from one-argument to two-argument and the call site was not updated — the work log needs to name the block definition, the call site, the arity mismatch, and the wrong-run count before and after. HourTab gives your Squeak retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log that names the block mechanism. No client login. No status emails. CSV in, URL out.

See HourTab pricing →

How HourTab tracks Squeak developer retainer hours

Squeak retainer work is invisible by the same mechanism that makes Squeak’s dynamic dispatch powerful: a block with the wrong arity is a valid object until it is called. The block definition stores the arity as metadata on the BlockClosure object, but there is no static verification that the call site matches. A simulation engine that runs 99 iterations correctly and fails on the 100th (because the 100th uses a code path that calls the block from a different location with a different arity) does not report an error until the 100th iteration. The error fires at the call site with a stack trace that shows the iteration method — not the method where the block was defined. Diagnosing the mismatch requires reading the stack trace to find the block activation, then opening the block’s source in the Browser to count its arguments.

The work log needs to name the mechanism: which block definition, which call site, what arity was expected versus what was passed, and the concrete before-and-after wrong-run count. A log entry that says “fixed block call in simulation, 3h” is not auditable. A log entry that says “Simulation update loop: updateBlock value: particle; block definition [:particle :dt | ...] expects 2 arguments; value: passes 1; Squeak raised WrongNumberOfArguments at the first simulation tick; fix: value: p value: timeStep; wrong simulation runs before: 3; after: 0; 3h” is auditable.

HourTab gives Squeak developers a public retainer-hours URL they send to clients — educational technology organizations running Etoys or Scratch-lineage environments, research institutions with Squeak simulation infrastructure, and companies with legacy Morphic applications. For Squeak retainers, each work log entry should name the Squeak-specific mechanism: which message send, which block arity, which Morphic update protocol, which image version. Comparative context: Squeak retainer work has conceptual overlap with other Smalltalk-family retainer work — Pharo (which shares Squeak heritage but has diverged in tooling; Pharo 12 uses a fully independent class library), GNU Smalltalk (which uses file-based loading rather than the image model), and VisualWorks (the commercial Smalltalk used in enterprise applications with its own image format). Squeak is distinct in its Morphic-centric architecture and its Etoys educational environment; the block arity and Morphic rendering issues are common across the family, but the image management and Etoys-specific APIs are Squeak-specific.

FAQ: Squeak developer retainers

What does a Squeak developer on retainer typically do?

A Squeak developer on monthly retainer covers BlockClosure arity diagnosis (WrongNumberOfArguments errors from value vs value: vs value:value: call site mismatches); Morphic rendering maintenance (debugging changed vs invalidRect: vs fullDrawOn: sequences, step rate and wantsSteps configuration, Morph layout issues); Squeak image management (maintaining .image + .changes pairs, recovering from image corruption, loading Monticello packages into clean images); class hierarchy maintenance (subclasses vs allSubclasses traversal, metaclass method lookup); and educational platform maintenance (Etoys, simulation engines, and Scratch-lineage environments built on Squeak Morphic).

What Squeak work is most commonly underlogged?

BlockClosure arity diagnosis is the most underlogged Squeak retainer work: tracing WrongNumberOfArguments to a call site that sends value: to a two-argument block; the error fires at the call site, not the definition; 3 to 6 hours of diagnosis produces a call site correction. Morphic rendering debugging: a morph that changes state but does not send changed, leaving a stale display; diagnosing the missing update requires tracing the damage-and-repair cycle; 4 to 8 hours invisible. Image restore debugging: a production image that fails to start cleanly because a startup action calls an API that was removed in a newer Squeak version; 5 to 10 hours invisible. Method lookup in the metaclass hierarchy: a class-side method shadowing an inherited method; 3 to 7 hours invisible.

What are typical Squeak developer retainer rates?

Entry-level Squeak developers with 1 to 2 years covering basic class and method definitions, simple Morphic subclassing, OrderedCollection and Dictionary usage, and block value evaluation typically bill at $60 to $110 per hour. Mid-level Squeak programmers with 2 to 4 years covering the full Morphic system, class hierarchy traversal, exception handling, thisContext stack inspection, and Squeak image management typically bill at $90 to $165 per hour. Senior Squeak developers with 4 or more years covering full Morphic layout and compositing, VM-level debugging, Pharo interoperability, and educational platform maintenance typically bill at $135 to $245 per hour. Monthly retainer ranges: $1,200 to $2,500 per month for advisory engagements (10 to 20 hours per month); $2,500 to $7,500 per month for full engagement Squeak application development or educational platform maintenance.

What should a Squeak developer retainer agreement include?

A Squeak developer retainer agreement should specify: Squeak version scope (Squeak 5.3, 6.0, 6.1, or Pharo which diverged in 2008 and has its own version series); platform scope (host OS, VM variant — CogVM, Spur image format, OpenSmalltalk VM); Morphic scope (base Squeak Morphic or Etoys-specific Morphic extensions); image management scope (who maintains the canonical .image file, whether the engagement covers image migration across versions, whether image corruption recovery is in scope); and hour logging format (the message sent, the block arity expected vs actual, the call site, the error message, the fix applied, the wrong-behavior count before and after).

How should Squeak developer retainer hours be logged?

Log each Squeak retainer session with: the message send that triggered the error (e.g., updateBlock value: particle — sending value: to a two-argument block); the block definition showing the arity (e.g., updateBlock := [:particle :dt | particle position: particle position + (particle velocity * dt)]); the WrongNumberOfArguments error text; the call site that was wrong vs the correct form (e.g., particles do: [:p | updateBlock value: p] should have been particles do: [:p | updateBlock value: p value: timeStep]); fix applied; wrong-run count before and after. For Morphic rendering bugs: the Morph method that changed state without sending changed, the visual symptom, the correct message sequence, fix applied, and wrong-render count before and after.