Workspace
Workspace — class-side facade over the running workspace (ADR 0129).
Workspace is a sealed, stateless class whose API is entirely class-side.
It works wherever a workspace runs (beamtalk run, workspace and
release modes) and raises a structured no_workspace error everywhere
else (e.g. beamtalk test). Use Workspace isAvailable to ask without
raising.
The no_workspace guard (beamtalk_capability:require_workspace/1) runs
once per call, at the backing module's delegate entry
(beamtalk_workspace_facade).
Examples
Workspace classes
Workspace load: "src/counter.bt"
Workspace isAvailable
Methods
- class » isAvailable
- class » classes
- class » testClasses
- class » bindings
- class » currentSession
- class » sessions
- class » sync
- class » changes
- class » load: path
- class » newClass: source at: path
- class » moveClass: aClass to: aNewPath
- class » flush
- class » flush: filter
- class » flush: filter confirmDestructive: confirmDestructive
- class » flushIncludingDestructive
- class » recheckImage
- class » test
- class » test: testClass
- class » bind: value as: bindingName
- class » unbind: bindingName
- class » startSupervisor: aClass
- class » stopSupervisor: aClass
- class » autoflush
- class » autoflush: enabled
- class » dependencies
Class Methods
True iff a workspace is running on this node. Never raises.
Examples
Workspace isAvailable // => true (in a workspace)
Return a list of loaded user classes with source file info.
Examples
Workspace classes
Return classes that are TestCase subclasses.
Examples
Workspace testClasses
Return a live BindingsView over the project namespace (ADR 0081).
Reads reflect the user-registered bindings (bind:as:) only. Class
facades such as Transcript are ordinary classes resolved through the
class registry, so they are not bindings. It is the same
BindingsView type as Session current bindings, so both binding layers
share one Dictionary protocol (at:, at:put:, removeKey:,
includesKey:, keys, values, size, do:).
Writes are synchronous: at:put: routes through bind:as: and
removeKey: through unbind:, inheriting their class-name conflict
checks (the class registry, Beamtalk allClasses, stays a List;
it is not a mutable binding layer.)
Examples
Workspace bindings includesKey: #Transcript // => false
Return the calling session as a Session, or nil outside a REPL eval
(ADR 0081 Phase 5).
A navigation alias for Session current: it returns the identical value
(the same Session carrying this session's id and bindings, or nil when
there is no session — e.g. compiled program code). There is no hasSession
predicate; guard with Workspace currentSession isNil or
Session current ifNotNil: [:s | ...].
Examples
Workspace currentSession // => a Session (in REPL) | nil
Workspace currentSession isNil // => false (in REPL)
Return a List of Session values, one per live shell (ADR 0081 Phase 7).
Each value is minted the same way Session withId: does, so it works with
the instance reads (s id, s bindings keys) and rejects cross-session
writes. This closes the gap left by Session withId: (which assumes you
already know the id): tooling discovers live session ids with
Workspace sessions collect: [:s | s id].
Examples
Workspace sessions // => #(a Session)
Workspace sessions collect: [:s | s id] // => #("user-repl-abc-123")
Sync the project: incrementally compile all changed files.
Scans the current working directory for a beamtalk.toml project
manifest, then finds .bt and .erl files, compiles changed files
in dependency order, and returns a result Dictionary.
The result Dictionary contains:
#summary— human-readable summary String#classes— List of loaded class name Strings#errors— List of error Dictionaries (empty on success)#changedCount— number of files reloaded#unchangedCount— number of unchanged files#deletedCount— number of deleted files
Examples
Workspace sync // => #{#summary => "Reloaded 2 of 5 files (3 unchanged)", ...}
(Workspace sync) at: #summary // => "Reloaded 2 of 5 files (3 unchanged)"
Return the workspace ChangeLog — the navigable view of pending in-memory changes (ADR 0082).
All pending-state queries live on the returned ChangeLog object, following
Pharo's Smalltalk changes idiom: "is anything dirty?" is
Workspace changes notEmpty; "what's dirty?" is
Workspace changes dirtyMethods.
Examples
Workspace changes notEmpty // => false
Workspace changes dirtyMethods // => #{}
Compile and load a .bt file, registering the class. Returns a List of loaded class objects (empty if none resolved). Returns a structured error if path is not a String or the file cannot be loaded.
Examples
Workspace load: "examples/counter.bt" // => [Counter]
Create a brand-new class from a source String at a target path (ADR 0082).
Compiles and installs the class in memory, then records a durable
kind: #'new-class' ChangeLog entry. Phase 1 does NOT write the file to
disk — that happens later on Workspace flush, which replays the entry to
write the initial file. Returns the loaded class object(s), like load:.
Raises a loud, specific error (no silent fallback) when:
pathalready exists on disk;pathlies outside the project source tree;- the declared class name does not match the basename of
path(one class per file, ADR 0040); - a class of that name is already loaded (use
compile:source:instead).
Examples
Workspace newClass: "Object subclass: Greeter" at: "src/greeter.bt"
// => [Greeter]
Move a class's .bt file to a new path without changing its name
(ADR 0114 Phase 2).
A pure filesystem-organization operation — unlike Behaviour>>renameTo:,
no cross-file reference needs rewriting, since every call site still
names the class as before; only where its .bt file lives on disk
changes. Records a durable kind: #'rename-class' ChangeLog entry
(old_class == class, signalling "same identity, different path") with
a single site — the class's own declaration. Does NOT write to disk —
that happens later on Workspace flush, which replays the entry as a
file move (ADR 0114 Phase 2 / BT-3271).
Raises no_source_file for a dynamic (ClassBuilder) class — moving
nothing is not a legitimate in-memory action. Raises a structured error
for a stdlib or dependency class (the same refusal renameTo: applies),
a target path outside the project source tree, or a target equal to the
class's own current path (a same-path move has nothing to move). Returns
aClass unchanged on success.
Examples
Workspace moveClass: Counter to: "src/models/counter.bt"
// => Counter
Flush every pending durable Tier 1 change to disk (ADR 0082 Phase 2; destructive tiering ADR 0113 Phase 2).
Writes each pending Tier 1 ChangeLog entry's patched body back to its
source file via byte-span splice (trivia-preserving — no AST reprint),
atomically (<file>.tmp + atomic rename), with external-edit conflict
detection. Tier 1 covers every entry that edits a still-existing file:
patches, newClass:at: creations, and removeSelector: removals (ADR
0112) — excising a recorded span leaves the file in place, the same
mechanical shape as a patch.
A pending Tier 2 entry (removeFromSystem's #'remove-class', ADR
0113) is not applied here — it deletes a file, and reaching that
tier always requires an explicit confirmDestructive gesture (see
flushIncludingDestructive / flush:confirmDestructive: below). It is
instead reported in skipped with #reason => #destructive.
Returns a FlushResult summary recording what was written, what was
skipped, and what conflicted:
flushed(Integer) — number of durable entries writtenfiles(List of String) — files touched (written or removed)newClasses(Integer) — subset offlushedfor new-class entriesremovedClasses(Integer) — subset offlushedfor remove-class entriesskipped(List) — pending entries left out of this flush, e.g.#{#seq => ..., #class => ..., #reason => #destructive}conflicts(List) — per-file conflict descriptors (#external_edit,#target_exists,#span_out_of_range, ...). A non-empty list means the listed entries remain pending and require manual reconciliation.
Quiet on success; loud only on conflicts and structured errors.
Examples
Workspace flush // => _ (flush summary)
Flush the pending Tier 1 ChangeLog entries matching filter (ADR 0082
Phase 2).
filter is one of:
- a Class — flush only entries targeting that class
- a Symbol (e.g.
#'new-class') — flush only entries of that kind - a Dictionary
#{#file => "path"}— flush only entries against that source file
A matching Tier 2 (#'remove-class') entry is reported in skipped,
not applied — use flush:confirmDestructive: to also apply it within
this scope. Returns the same FlushResult shape as flush.
Examples
Workspace flush: Counter // => _
Workspace flush: #'new-class' // => _
Workspace flush: #{ #file => "src/counter.bt" } // => _
Flush the pending ChangeLog entries matching filter, additionally
applying Tier 2 (#'remove-class') entries within that scope
(ADR 0113 Phase 2).
confirmDestructive must be a literal true/false — never read from
a workspace setting or environment variable, so reaching the
destructive tier always names its own consent at the call site. The
class/kind/file argument gives confirmDestructive: a real keyword
partner, keeping this an ordinary two-keyword message.
Examples
Workspace flush: Counter confirmDestructive: true // => _
Flush every pending durable+flushable ChangeEntry, Tier 1 and Tier 2 (ADR 0113 Phase 2).
The unscoped destructive-flush entry point — a bare unary selector, not
a keyword message, since there is no class/kind/file argument to attach
a confirmDestructive: keyword to once the call has no scope. Applies
every pending Tier 1 entry (as flush does) plus every pending Tier 2
(#'remove-class') entry, deleting the corresponding .bt files.
Returns the same FlushResult shape as flush, with skipped empty
for every entry this call was able to reach.
Examples
Workspace flushIncludingDestructive // => _
Whole-image re-check (ADR 0105 Phase 3): re-check every live class the workspace has a recorded source for, not just the dependents of one changed selector.
The "complete but unbounded" path ADR 0105 keeps out of the automatic
post-reload check — that check only re-checks the xref-filtered
dependents of whichever selector just changed, capped per reload; this
is an explicit, on-demand sweep of the whole image with no cap. Also
available as the REPL :recheck image alias.
Returns a Dictionary:
checked(Integer) — classes a re-check round-trip completed forstale(Integer) — distinct classes with at least one findingfindings(List of Dictionary) — each withowner,severity,category,message,start,end
Examples
Workspace recheckImage // => _ (checked/stale summary)
Run all loaded test classes and return a TestResult.
Examples
Workspace test
Run a specific test class and return a TestResult.
Examples
Workspace test: CounterTest
Register a value in the workspace namespace under a given name. Subsequent REPL evals can reference the value by name. Raises an error if name is already a registered class (system name conflict). Warns if name is an existing loaded class (use reload instead).
Examples
Workspace bind: myActor as: #MyTool
Remove a registered name from the workspace namespace. Raises an error if name is not found.
Examples
Workspace unbind: #MyTool
Start and attach a supervisor to the workspace supervision tree.
The class must be a Supervisor or DynamicSupervisor subclass. Idempotent: returns the existing instance if already attached. Supports iterative development — stop and re-attach after reloading.
Examples
Workspace startSupervisor: MySup // => Supervisor(MySup, 0.200.0)
Workspace startSupervisor: MySup // => Supervisor(MySup, 0.200.0) (idempotent)
Stop and remove a supervisor from the workspace.
Works for both workspace-attached supervisors and the root application supervisor. Cleanly shuts down the supervisor and all its children. Raises an error if the supervisor is not running or not visible.
Examples
Workspace stopSupervisor: MySup // => nil
Read the autoflush workspace setting (ADR 0082 Phase 4).
Default is false. When true, every successful durable in-memory patch
(via >> / compile:source: / newClass:at:) immediately triggers a
Workspace flush after the install, collapsing the model to write-through
at the workspace level. The setting persists across workspace restarts.
Autoflush is best-effort: on flush failure (external-edit conflict, write error) memory and disk diverge and the ChangeEntry remains in the log for manual reconciliation — the BEAM module install is not rolled back (live actors may hold references to the new closures).
Examples
Workspace autoflush // => false
Set the autoflush workspace setting (ADR 0082 Phase 4).
enabled must be a Boolean. Persists to the workspace metadata.json so
the setting survives workspace restart. Returns the new value so the
caller can confirm the effective state.
Examples
Workspace autoflush: true // => true
Workspace autoflush: false // => false
Return a Dictionary of direct dependency packages for the current workspace.
Keys are package name Strings, values are Package objects. Returns an empty Dictionary if the workspace has no declared dependencies.
Examples
Workspace dependencies
// => _
Inherited Methods
From Object
Return the class of the receiver.
Examples
42 class // => Integer
"hello" class // => String
Test if the receiver is nil. Returns false for all objects except nil.
Examples
42 isNil // => false
nil isNil // => true
Test if the receiver is not nil. Returns true for all objects except nil.
Examples
42 notNil // => true
nil notNil // => false
If the receiver is nil, evaluate nilBlock. Otherwise return self.
_nilBlock is never evaluated on this Object-level definition (only
UndefinedObject's override calls it, with Block(R) -> R), so R is
unconstrained here — parameterizing it still documents the zero-arg
shape without implying this branch produces R (BT-2834).
Examples
42 ifNil: [0] // => 42
nil ifNil: [0] // => 0
If the receiver is not nil, evaluate notNilBlock with self.
Left as a bare Block deliberately (BT-2834): notNilBlock is invoked
one-arg with self and its result becomes the method's own result, so
a precise signature needs a Self-in-Block(...) type-arg form
(Block(Self, R) -> R) with no precedent elsewhere in stdlib. The type
checker doesn't consult this declared signature for ifNotNil: anyway
— it narrows the block's parameter from the receiver's own type via
infer_args_for_if_not_nil in inference.rs (BT-2046), independent of
this stdlib declaration.
Examples
42 ifNotNil: [:v | v + 1] // => 43
nil ifNotNil: [:v | v + 1] // => nil
If nil, evaluate nilBlock; otherwise evaluate notNilBlock with self.
Bare Block params here (unlike UndefinedObject's Block(R) -> R
override, BT-2824) are intentional, not an oversight: the type checker
never consults this declared signature for these two selectors — it
reads the block arguments' actual inferred return types directly
(if_nil_branch_union_ret_ty in inference.rs, BT-2047) since a bare
-> R here can't express "whatever the block returns" without a
Self-in-Block(...) type-arg form with no precedent elsewhere in
stdlib.
Examples
42 ifNil: [0] ifNotNil: [:v | v + 1] // => 43
nil ifNil: [0] ifNotNil: [:v | v + 1] // => 0
If not nil, evaluate notNilBlock with self; otherwise evaluate nilBlock.
See ifNil:ifNotNil: above — the bare Block params are intentional.
Examples
42 ifNotNil: [:v | v + 1] ifNil: [0] // => 43
nil ifNotNil: [:v | v + 1] ifNil: [0] // => 0
Return the developer-readable (Debug) string representation.
printString is the Debug protocol (ADR 0094): the self-describing,
structural form used by the REPL, logs, and by any other printString
that nests this object. It is the REPL default — evaluating an expression
shows its printString.
This default returns the bare class name (no a/an article — the
old "a ClassName" form was dropped in ADR 0094). Value overrides it
with the structural ClassName(field: value, ...) form, actors render as
Actor(ClassName, pid), supervisors as Supervisor(ClassName, pid) /
DynamicSupervisor(ClassName, pid), and primitive types (Integer, String,
List, …) override it with their own richer output. Authors rarely override
printString directly — the default is derived.
Examples
42 printString // => "42"
Return the user-facing (Display) string representation.
displayString is the Display protocol (ADR 0094): the human-facing
form. It is the hook the language pulls during string interpolation —
every {...} segment renders via the value's displayString. Developers
rarely call it directly; they override it when a value has a natural
human rendering (e.g. Money → $10.50, where printString would still
show the Debug form).
It defaults to printString, so most types need no override. String
and Symbol demonstrate the split: "hi" printString → "\"hi\""
(quoted, Debug) while "hi" displayString → "hi" (plain, Display);
likewise #foo drops its # prefix under displayString.
displayString is not part of the Printable protocol (deferred per
ADR 0094 §5).
Examples
42 displayString // => "42"
Open a navigable Inspector cursor on the receiver.
ADR 0095 Phase 3 (BT-2504). inspect is repurposed from -> String
(the ADR-0094 deferral) to the verb that produces an Inspector — a
live, immutable cursor for drilling into the object (Inspector on: self).
anObject inspect is the shorthand; Inspector on: anObject is the
explicit spelling. The cursor exposes fields/at:/path/refresh/
printString (an indented text tree) and asDictionaries (the MCP/browser
wire form); see Inspector.
This is a breaking change: code that used inspect for its old
String result must switch to printString (the structural Debug string,
ADR 0094) — a transitional lint flags inspect used directly in ++/
string position.
Examples
42 inspect kind // => #value
(Point x: 3 y: 4) inspect fields size // => 2
(Point x: 3 y: 4) printString // => "Point(x: 3, y: 4)" (the old inspect string)
Return the receiver itself. Useful for cascading side effects.
Examples
42 yourself // => 42
Return a hash value for the receiver.
Examples
42 hash
Test value equality — the overridable counterpart to =:=.
Defaults to =:= (Erlang structural term equality), so for most classes
the two agree. Unlike =:=, this is an ordinary message send, so a class
whose logical value is not its representation can override it: a
persistent structure whose shape depends on construction history, a
timestamp that should compare by instant rather than by wall-clock fields,
or a type with a normalising form. =:=, =/=, == and /= are lowered
straight to Erlang BIFs (ADR 0002) and cannot be overridden — the compiler
rejects any attempt (BT-2997).
What honours it
The linear scans: includes: on Collection, List, Array and
Dictionary (which searches values), plus List>>indexOf: and
TestCase>>assert:equals:.
Contract
An override must agree with =:= wherever =:= holds — a =:= b must
imply a equals: b. It may only make more values equal, never fewer.
The scans above rely on this: each tries raw =:= first and dispatches
only on a miss, so a fast-path hit is always a genuine equals: hit.
Caveat
Set, Dictionary keys, and List>>unique decide identity in the VM
with raw =:=, never through this method — keying and deduplication need
an order or a hash that a user-defined equals: cannot supply. A class
that overrides equals: still deduplicates by representation when used
as a key or element. Normalise the representation if that matters.
Examples
42 equals: 42 // => true
42 equals: 42.0 // => false (defaults to strict `=:=`)
"ab" equals: "ab" // => true
Test if the receiver responds to the given selector.
Examples
42 respondsTo: #abs // => true
Return the names of fields.
Examples
42 fieldNames // => #()
Return the value of the named field.
Left without a declared return type deliberately (BT-2834 pattern,
see Block>>value): the field's actual value can be any type, and
callers routinely send it further messages ((self fieldAt: #x) + 1)
that a fixed -> Object would statically reject.
Examples
object fieldAt: #name
Set the value of the named field (returns new state).
Examples
object fieldAt: #name put: "Alice"
Test whether the receiver has the named field. Never raises — the
non-raising counterpart to fieldAt:, in particular for a late
slot (ADR 0124): fieldAt: raises UninitializedStateError reading
an unassigned late slot, hasField: is the question to ask first.
Examples
c := CodexClient start: "/tmp/ws" config: cfg onEvent: nil
c hasField: #proc // => false — declared `late`, not yet assigned
c launch
c hasField: #proc // => true
42 hasField: #anything // => false — primitives have no fields
Return a late field to the unassigned state (ADR 0124), so it can be
assigned again (e.g. re-acquiring a resource). Joins fieldAt:/
fieldAt:put:/fieldNames in ADR 0035's field-prefixed reflection
family. Returns self.
Examples
c clearField: #proc
c hasField: #proc // => false — relaunchable
Send a unary message dynamically.
Left without a declared return type deliberately (BT-2834 pattern,
see fieldAt: above): the invoked method's return type is unknown
statically, and a fixed -> Object would block legitimate chained
sends on the real (narrower) result.
Examples
42 perform: #abs // => 42
Send a message dynamically with arguments.
See perform: above for why no return type is declared.
Examples
3 perform: #max: withArguments: #(5) // => 5
Raise an error indicating this method must be overridden by a subclass.
Examples
self subclassResponsibility
Raise an error indicating this method has not yet been implemented.
Use this for work-in-progress stubs. Distinct from subclassResponsibility,
which signals an interface contract violation.
Examples
self notImplemented
Send aValue to Transcript without a trailing newline.
Outside an interactive workspace this is a Logger notice (ADR 0129 §5).
Examples
42 show: "value: "
Send aValue to Transcript followed by a newline.
Outside an interactive workspace this is a Logger notice (ADR 0129 §5).
Examples
42 showCr: "hello world"
Test if the receiver is an instance of aClass or any of its subclasses.
For class-object receivers, follows Smalltalk semantics: self class
is the metaclass, so the check walks the parallel metaclass hierarchy.
The parallel chain is grounded at ProtoObject class superclass == Class
(ADR 0036), so the metaclass tower merges into the instance-side
Class → Behaviour → Object → ProtoObject chain. As a result,
Integer isKindOf: Object and Integer isKindOf: Class both return true.
Examples
42 isKindOf: Integer // => true
42 isKindOf: Object // => true
#foo isKindOf: Symbol // => true
#foo isKindOf: String // => false
Integer isKindOf: Number // => false (metaclass chain, not instance chain)
Integer isKindOf: Number class // => true (Number class is in the parallel chain)
Integer isKindOf: Object // => true (grounded — Object is reachable via the metaclass tower)
Integer isKindOf: Class // => true (Integer class inherits from Class)
Raise an error with the given message.
Examples
self error: "something went wrong"
Delegate message dispatch to the backing Erlang module (ADR 0101, BT-2720).
This method is a sentinel — a plain Object has no backing Erlang module,
so calling delegate raises an Error at runtime. Stateless Objects
declared with native: have their self delegate method bodies rewritten
by the compiler's codegen phase to call the backing module directly, so the
sentinel is never reached on a native: class.
Unlike Actor's delegate (visible only to Actor subclasses), this
Object-base sentinel is visible to every class, so delegate is a
reserved selector on the Object protocol.
Examples
42 delegate // => ERROR: delegate called on a non-native Object
From ProtoObject
Test value equality (Erlang ==, non-strict — 1 == 1.0 is true).
Examples
42 == 42 // => true
"abc" == "abc" // => true
Test value inequality (negation of ==).
Examples
1 /= 2 // => true
42 /= 42 // => false
Test strict value equality (Erlang =:= — 1 =:= 1.0 is false, unlike ==).
Every object supports this at runtime (it lowers directly to Erlang's
=:= operator regardless of receiver type, ADR 0002) — declared here,
once, so it is visible in method listings/completions/respondsTo: for
every class, not just the handful (Integer, String, ...) that
separately redeclare it with a narrower, class-typed other for
documentation purposes.
Examples
1 =:= 1 // => true
1 =:= 1.0 // => false (strict — different types)
(Dictionary new) =:= #{} // => true
Test strict value inequality (negation of =:=).
Examples
1 =/= 1.0 // => true (strict — different types)
1 =/= 1 // => false
Return the class of the receiver.
Examples
42 class // => Integer
"hello" class // => String
Handle messages the receiver does not understand. Override for custom dispatch.
No declared return type (BT-2834 pattern, see Object>>fieldAt:): a
custom override can return any type, and callers chain further sends
on the result that a fixed -> Object would statically reject.
Examples
42 unknownMessage // => ERROR: does_not_understand
Send a message dynamically with an arguments list.
Examples
42 perform: #abs withArguments: #() // => 42
Execute a class method in the caller's process, bypassing gen_server dispatch.
The caller takes responsibility for knowing the method does not mutate class state. Useful for long-running class methods that would otherwise block the class object's gen_server.
Limitations: only resolves methods defined directly on the target class
module (does not walk the superclass chain). Class variables and self
are not available to the method (nil and #{} are passed).
Examples
MyClass performLocally: #run:ctx: withArguments: #(input, ctx)
Send a message dynamically with an arguments list and explicit timeout.
The timeout (in milliseconds or #infinity) applies to the gen_server:call
when the receiver is an actor. For value types, timeout is ignored.
Examples
actor perform: #query withArguments: #(sql) timeout: 30000