ClassBuilder
ClassBuilder — Fluent builder for creating and registering Beamtalk classes.
ClassBuilder is an Actor subclass — each builder instance is a short-lived
gen_server process that accumulates configuration via cascaded messages.
The register method is a terminal operation: it registers the class with
the runtime, stops the builder process, and returns the new class object.
Codegen emits Core Erlang that calls ClassBuilder directly — the Beamtalk snippet below is conceptual:
Object classBuilder
name: #Counter;
fields: #{ #count => 0 };
methods: #{ #increment => [...], #value => [...] };
register
References
- ADR 0038:
docs/ADR/0038-subclass-classbuilder-protocol.md - Pattern:
stdlib/src/Actor.bt(ClassBuilder is Actor subclass)
Methods
- name: aSymbol
- superclass: aClass
- fields: aDict
- methods: aDict
- methodSource: aDict
- classMethods: aDict
- classMethodSource: aDict
- classVars: aDict
- methodSignatures: aDict
- classMethodSignatures: aDict
- methodReturnTypes: aDict
- classMethodReturnTypes: aDict
- methodDocs: aDict
- classMethodDocs: aDict
- classDoc: aString
- meta: aDict
- isConstructible: aBoolean
- addField: aName default: aValue
- addMethod: aSelector body: aBlock
- addClassMethod: aSelector body: aBlock
- addClassState: aName default: aValue
- removeMethod: aSelector
- removeClassMethod: aSelector
- modifier: aSymbol
- native: anErlangModule
- register
Instance Methods
Set the class name (a Symbol).
Examples
builder name: #Counter
Set the superclass reference.
Examples
builder superclass: Object
Set all fields at once (used by codegen).
Examples
builder fields: #{ #count => 0 }
Set all methods at once (used by codegen).
Examples
builder methods: #{ #increment => fun }
Set per-method source text as a selector => source string map.
Populated automatically by the compiler from block-literal method
bodies (BT-2246) so builder-defined classes are visible to
SystemNavigation source-text queries, just like file-defined ones.
Methods built from computed/non-literal funs have no source literal
and are simply omitted (known-present but unindexable).
Examples
builder methodSource: #{ #increment => "increment => self.count := self.count + 1" }
Set all class-side methods at once (ADR 0084, BT-2267).
Each value is a class-method block whose body the compiler lowers with
the class-method calling convention (fun(ClassSelf, ClassVars, ...)),
so class-variable mutations thread correctly and super/self resolve.
The runtime installs them as runtime class-method funs (BT-2266).
Examples
builder classMethods: #{ #bump => [:self | self.total := self.total + 1] }
Set per-class-method source text as a selector => source string map.
The class-side counterpart of methodSource:. Populated automatically by
the compiler from classMethods: block-literal bodies (BT-2270) so
builder-defined class methods are visible to SystemNavigation
source-text queries on the class side (BT-2195), just like file-defined
ones. Class methods built from computed/non-literal funs have no source
literal and are simply omitted (known-present but unindexable).
Examples
builder classMethodSource: #{ #bump => "bump => self.total := self.total + 1" }
Set class-variable initial values (ADR 0084, BT-2267).
The class-side counterpart of fields:. Named classVars: rather than
classState: because classState: is a reserved class-variable
declaration keyword (it cannot be a method selector), mirroring how the
instance-side setter is fields:, not the reserved field:/state:.
Examples
builder classVars: #{ #total => 0 }
Set instance-method display signatures as a selector => signature map
(ADR 0084, BT-2268).
These are the human-readable signatures shown by :help (e.g.
"increment -> Integer"). The file-defined path derives them from the
method AST; the builder path supplies them explicitly so programmatically
built classes reach :help parity.
Examples
builder methodSignatures: #{ #increment => "increment -> Integer" }
Set class-side method display signatures as a selector => signature map
(ADR 0084, BT-2268).
The class-side counterpart of methodSignatures:, shown by :help for
class methods.
Examples
builder classMethodSignatures: #{ #default => "default -> Counter" }
Set instance-method return-type metadata as a selector => type map
(ADR 0084, BT-2268).
Mirrors the return-type channel the compiler emits for file-defined classes so reflective type queries return the same data for builder-defined ones.
Examples
builder methodReturnTypes: #{ #increment => #Integer }
Set class-side method return-type metadata as a selector => type map
(ADR 0084, BT-2268).
The class-side counterpart of methodReturnTypes:.
Examples
builder classMethodReturnTypes: #{ #default => #Counter }
Set per-method doc comments as a selector => doc string map
(ADR 0084, BT-2268).
These are the doc comments reflected by documentation queries and shown in
:help, matching the doc channel the compiler emits for file-defined
classes.
Examples
builder methodDocs: #{ #increment => "Add one to the count." }
Set per-class-method doc comments as a selector => doc string map
(ADR 0084, BT-2268).
The class-side counterpart of methodDocs:.
Examples
builder classMethodDocs: #{ #default => "Build a fresh counter." }
Set the class-level doc comment (ADR 0084, BT-2268).
Reflected by documentation queries on the class, matching the classDoc
channel the compiler emits for file-defined classes.
Examples
builder classDoc: "A simple mutable counter."
Set the class meta map (ADR 0084, BT-2268).
The meta map carries static class metadata (flags, fields, method info)
that the runtime reads during registration. Left unset, the runtime falls
back to reading the backing module's __beamtalk_meta/0.
Examples
builder meta: #{ #is_abstract => false }
Set whether instances of the class can be created via new / new:
(ADR 0084, BT-2268).
File-defined classes infer this from the new => self error: pattern;
the builder path sets it explicitly. Leaving it unset (nil) lets the
runtime compute constructibility lazily, as it does for primitive classes.
Examples
builder isConstructible: false
Add a single field with a default value.
Examples
builder addField: #name default: 'Rex'
Add a single instance method from a block body (the "browser" use case).
The incremental counterpart of methods:. It writes the same
methodSpecs map the bulk setter populates, so a class can be assembled
one method at a time before register. In a classBuilder … register
cascade the compiler lowers the block argument exactly as it lowers a
methods: map value — the method is installed as a compiler-generated
runtime fun, not a naive closure (BT-873). A computed (non-literal) fun is
stored verbatim and dispatches with the fun(Self, Args…) convention.
Examples
builder addMethod: #answer body: [:self | 42]
Add a single class-side method from a block body (ADR 0084, BT-2267).
The incremental counterpart of classMethods:. It writes the same
classMethods map, so a class can gain class methods one at a time before
register. In a classBuilder … register cascade the compiler lowers the
block with the class-method calling convention (fun(ClassSelf, ClassVars, …)) so class-variable mutations thread and super/self resolve, just
like a classMethods: map value.
Examples
builder addClassMethod: #bump body: [:self | self.total := self.total + 1]
Add a single class variable with a default value (ADR 0084, BT-2267).
The incremental counterpart of classVars:. It writes the same
classState map. Named addClassState:default: rather than
addClassVar:default: for symmetry with the classState: declaration
keyword used on file-defined classes.
Examples
builder addClassState: #total default: 0
Remove a single instance method before register (live-editing parity).
Drops the selector from the methodSpecs map. A no-op when the selector
was never added.
Examples
builder removeMethod: #answer
Remove a single class-side method before register (live-editing parity).
Drops the selector from the classMethods map. A no-op when the selector
was never added.
Examples
builder removeClassMethod: #bump
Apply a class modifier (#abstract, #sealed, #typed, #internal).
Examples
builder modifier: #sealed
Set the backing Erlang module for a native Actor class.
When set, the register call will link this Beamtalk class to the
named Erlang module, enabling self delegate dispatch.
Examples
builder native: #beamtalk_subprocess
Register the class with the runtime and return the new class object. Stops the builder process after registration — the builder is single-use and should not be retained.
Called from compiled module init, wires the BEAM module's pre-compiled
methods into the class gen_server. If the class already exists (hot
reload), updates the existing gen_server state via update_class/2
rather than failing.
Inherited Methods
From Actor
Register this (already-spawned) actor under name.
Non-atomic with respect to spawn: another process may claim name
between the actor's spawn and this call. Prefer class spawnAs: when
the name is known at spawn time.
On success returns Result ok: self so calls can be chained:
(counter registerAs: #c) onSuccess: [:c | c increment]
Unregister this actor's name, if any. Idempotent.
Returns #ok even when the actor has no registered name or the name has
already been released. When a real failure occurs (reserved name, type
error, etc.) the error is raised — consistent with other teardown methods
like stop and kill.
When an actor process exits, Erlang automatically releases its registered
name; callers do not need to call unregister from terminate:.
Examples
counter unregister // => #ok
Return the Symbol this actor is registered under, or nil if unnamed.
Examples
counter registeredName // => #counter (or nil)
Whether this actor currently has a registered name.
Examples
counter isRegistered // => true or false
Wrap this actor with a custom message timeout.
Returns a TimeoutProxy that forwards all messages to this actor using
the given timeout (in milliseconds, or #infinity) for gen_server:call.
The default OTP timeout is 5000ms.
The proxy is a separate actor process — call stop on it when done.
Examples
slowDb := db withTimeout: 30000
slowDb query: sql // forwarded with 30s timeout
slowDb stop // stop the proxy when done
Optional lifecycle hook called automatically after spawn.
Override in subclasses to perform setup that goes beyond state: defaults,
such as opening resources or computing derived state. Called synchronously
before the spawned object is returned to the caller.
If initialize raises an error, the spawn fails with a catchable
InstantiationError. Under a supervisor, the child start fails and the
supervisor applies its restart strategy.
Examples
Actor subclass: Stack
state: items = nil
initialize => self.items := #()
Optional lifecycle hook called when the actor is shutting down.
Override in subclasses to perform cleanup such as closing resources,
flushing buffers, or notifying dependents. Called synchronously during
graceful shutdown (stop). The reason parameter is an Object — it may
be a Symbol like #normal for graceful stop, but OTP can also pass
compound terms like {shutdown, term} for non-atom shutdown reasons.
Not called when the actor is forcefully killed (kill).
If terminate: raises an error, shutdown proceeds anyway.
Actor state (self.field) is accessible during terminate:.
Examples
Actor subclass: Logger
state: logFile = nil
terminate: reason =>
self.logFile isNil ifFalse: [self.logFile close]
Delegate message dispatch to the backing Erlang module.
This method is a sentinel — non-native Actors do not have a backing
Erlang module, so calling delegate raises an Error at runtime.
Native Actors (declared with native: in ClassBuilder) override this
intrinsic via the compiler's codegen phase.
Examples
counter delegate // => ERROR: delegate called on a non-native Actor
Return the raw Erlang PID backing this actor.
Useful for FFI interop where an Erlang function expects a raw pid.
Examples
rawPid := counter pid
rawPid class // => Pid
Create an Erlang monitor on this actor's process.
Returns a Reference that can be used to cancel the monitor via
demonitor. The caller will receive a DOWN message if the
actor exits.
Examples
ref := counter monitor
ref class // => Reference
ref demonitor // cancel the monitor
Register a callback to be invoked when this actor exits.
Monitors the actor and calls block value: reason when the actor
process terminates. Returns #ok immediately. The block is called
asynchronously from a lightweight watcher process.
Examples
worker onExit: [:reason |
Logger info: "worker exited" metadata: #{"reason" => reason displayString}
]
Gracefully stop this actor (gen_server:stop).
Idempotent: stopping an already-stopped actor succeeds silently. Raises an error if the actor times out during shutdown.
Examples
counter stop // => ok
Forcefully kill this actor (exit(Pid, kill)).
Unlike stop, kill cannot be trapped by the actor process.
Examples
counter kill // => ok
Check if this actor's process is still alive.
WARNING: isAlive check-then-act is inherently racy. The actor could die between the isAlive check and a subsequent message send. Use monitors for robust lifecycle management.
Examples
counter isAlive // => true or false
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.
Examples
42 ifNil: [0] // => 42
nil ifNil: [0] // => 0
If the receiver is not nil, evaluate notNilBlock with self.
Examples
42 ifNotNil: [:v | v + 1] // => 43
nil ifNotNil: [:v | v + 1] // => nil
If nil, evaluate nilBlock; otherwise evaluate notNilBlock with self.
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.
Examples
42 ifNotNil: [:v | v + 1] ifNil: [0] // => 43
nil ifNotNil: [:v | v + 1] ifNil: [0] // => 0
Return a developer-readable string representation.
Default implementation returns "a ClassName". Subclasses such as
Integer, String, and List override this to return richer output.
Examples
42 printString // => "42"
Return a user-facing string representation for display purposes.
Default implementation delegates to printString. Subclasses such as
String and Symbol override this to return a more readable form without
developer annotations (e.g. no surrounding quotes or # prefix).
Examples
42 displayString // => "42"
Inspect the receiver.
Examples
42 inspect // => "42"
Return the receiver itself. Useful for cascading side effects.
Examples
42 yourself // => 42
Return a hash value for the receiver.
Examples
42 hash
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.
Examples
object fieldAt: #name
Set the value of the named field (returns new state).
Examples
object fieldAt: #name put: "Alice"
Send a unary message dynamically.
Examples
42 perform: #abs // => 42
Send a message dynamically with arguments.
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 the current transcript without a trailing newline.
Nil-safe: does nothing when no transcript is set (batch compile, tests).
Examples
42 show: "value: "
Send aValue to the current transcript followed by a newline.
Nil-safe: does nothing when no transcript is set (batch compile, tests).
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"
From ProtoObject
Test value equality (Erlang ==).
Examples
42 == 42 // => true
"abc" == "abc" // => true
Test value inequality (negation of ==).
Examples
1 /= 2 // => true
42 /= 42 // => 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.
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