Actor
Actor — Base class for process-based objects.
Actor inherits from Object and adds process-based concurrency.
Every actor runs in its own BEAM process and communicates via
asynchronous messages. Use spawn instead of new to create actors.
Examples
Actor subclass: Counter
state: count = 0
increment => self.count := self.count + 1
count => self.count
@see Value (for immutable data without a process) @see Supervisor (for static supervision trees) @see DynamicSupervisor (for dynamic supervision trees)
Methods
- class » spawn
- class » spawnWith: initArgs
- class » new
- class » new: _initArgs
- class » spawnAs: name
- class » spawnWith: initArgs as: name
- class » named: name
- class » allRegistered
- class » supervisionPolicy
- class » isSupervisor
- class » supervisionSpec
- registerAs: name
- unregister
- unregisterName
- registeredName
- isRegistered
- withTimeout: ms
- initialize
- terminate: _reason
- delegate
- pid
- monitor
- onExit: block
- stop
- kill
- isAlive
Class Methods
Spawn a new actor process with default state.
The compiler's static Counter spawn and self spawn call sites lower
directly to beamtalk_actor:safe_spawn/class_self_spawn (BT-3072) —
this declaration is the documented, xref-visible source of behaviour for
the dynamic dispatch path (workspace bindings, perform:), not the
method actually invoked by those fast paths.
Examples
c := Counter spawn
Spawn a new actor process with initialization arguments.
See spawn for the static-vs-dynamic dispatch note (BT-3072).
Examples
c := Counter spawnWith: #{#count => 10}
Actors cannot be created with new — always raises (BT-217).
Actors are processes with identity and lifecycle, not immutable
values; spawn starts the process, new does not make sense for
them. Every actor subclass carries a compiled new/0 that raises this
same error directly (required by the runtime's {new, Args}
instantiation protocol, which calls a class's own compiled new/0
rather than dispatching through inherited class methods) — this
declaration is the documented, xref-visible source of that behaviour.
Examples
Counter new // => raises instantiation_error: "Use spawn instead"
Actors cannot be created with new: — always raises (BT-217).
Use spawnWith: to create an actor instance with initialization
arguments. See new for why actors reject direct instantiation.
Examples
Counter new: #{#count => 10} // => raises instantiation_error: "Use spawnWith: instead"
Atomically spawn a new actor and register it under name.
Uses gen_server:start_link({local, Name}, ...) under the hood so the
name is established during process startup — no TOCTOU window between
spawn and registerAs:. Prefer this over post-spawn registerAs:
whenever the name is known up front.
Returns Result ok: actor on success, or Result error: with one of:
name_registered— another process is already registered undernamereserved_name—nameis in the OTP kernel / stdlib blocklisttype_error—nameis not a Symbol
Examples
c := (Counter spawnAs: #counter) unwrap
(Counter spawnAs: #counter) onError: [:e | Logger warn: "name taken"]
Atomically spawn a new actor with init args and register it under name.
Same contract as spawnAs: plus the initialisation arguments passed to
the actor's init/1 callback.
Examples
c := (Counter spawnWith: #{#count => 10} as: #counter) unwrap
Look up a registered actor by name, checked against the receiver class.
Returns Result ok: actor when a process is registered under name and
its class is the receiver (or any subclass). Self resolves to the
receiver class at the call site, so subclasses inherit a typed lookup:
Counter named: #counter // => Result(Counter, Error)
Actor named: #anything // => Result(Actor, Error)
Error cases:
name_not_registered— nothing registered undernamewrong_class— registered, but not a (subclass of) the receiver classtype_error—nameis not a Symbol
Examples
engine := (WorkflowEngine named: #workflowEngine) unwrap
(Logger named: #counter) // => Result error: (beamtalk_error wrong_class)
Return every currently-registered Beamtalk actor as Actor proxies.
Filters out raw Erlang-registered processes (kernel, logger, user FFI
registrations) — only actors carrying the '$beamtalk_actor' marker
appear. Each returned proxy carries its real class, so
Actor allRegistered first class returns the concrete subclass, not
Actor.
Intended for tooling and REPL discovery; production code should use
Actor named: to address known names directly.
Examples
Actor allRegistered // => #(an Actor(Counter), an Actor(Logger))
Default OTP restart policy for this actor class.
Returns #temporary by default — the actor is not automatically restarted
on crash. Override in subclasses to declare a different default:
#permanent— always restart (e.g., a database pool)#transient— restart only on abnormal exit (e.g., a request handler)#temporary— never restart (the default)
Examples
Actor supervisionPolicy // => #temporary
DatabasePool supervisionPolicy // => #permanent (if overridden)
Whether this class is a supervisor.
Returns false for Actor subclasses. Supervisor and DynamicSupervisor
subclasses override this to return true. Used by SupervisionSpec childSpec
to determine OTP type (#worker vs #supervisor) and shutdown timeout.
Examples
Counter isSupervisor // => false
WebApp isSupervisor // => true (if WebApp subclasses Supervisor)
Return a SupervisionSpec for this actor class with default settings.
Creates a SupervisionSpec with actorClass set to the receiver and
restart set from supervisionPolicy. Use the fluent with*: API to
override per-child settings:
Examples
DatabasePool supervisionSpec
// => SupervisionSpec with actorClass: DatabasePool, restart: #temporary
DatabasePool supervisionSpec withId: #primary withArgs: #{#role => #primary}
Instance Methods
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
Internal FFI seam (ADR 0101 Part 4): release this actor's registered
name. Keeps unregister pure Beamtalk.
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 (milliseconds, a Duration, 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
patient := db withTimeout: (Duration seconds: 30)
patient stop
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
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.
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"
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.
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