Actor

Inherits from Object

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)

Class Methods

spawn Sealed source

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
spawnWith: initArgs Sealed source

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}
new Sealed source

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"
new: _initArgs Sealed source

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"
spawnAs: name Sealed source

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 under name
  • reserved_name — name is in the OTP kernel / stdlib blocklist
  • type_error — name is not a Symbol

Examples

c := (Counter spawnAs: #counter) unwrap
(Counter spawnAs: #counter) onError: [:e | Logger warn: "name taken"]
spawnWith: initArgs as: name Sealed source

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
named: name Sealed source

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 under name
  • wrong_class — registered, but not a (subclass of) the receiver class
  • type_error — name is not a Symbol

Examples

engine := (WorkflowEngine named: #workflowEngine) unwrap
(Logger named: #counter) // => Result error: (beamtalk_error wrong_class)
spawnAs: name scope: scope Sealed source

Atomically spawn a new actor and register it under name, in the requested cluster scope.

scope: #local behaves identically to spawnAs: — genuinely atomic: a losing race never runs initialize at all (OTP reserves the local name as part of the process start itself). scope: #global registers with OTP's global module instead — the name is unique across the whole connected cluster and resolves the same way from every node, via global:whereis_name/1 on each send rather than a per-node lookup — but is NOT atomic in the same way: global has no custom-resolver variant of an atomic "start-and-register", so the actor is started (and initialize runs to completion) before the name is registered. If that registration then loses the race, the already-initialized actor is torn down and name_registered is returned — but any side effect initialize already performed (I/O, messaging another actor, etc.) already happened, even though the caller sees the same clean failure #local would give without ever running initialize. Idempotent initialize overrides are unaffected; one with externally-visible side effects should account for this under scope: #global.

Returns Result ok: actor on success, or Result error: with one of:

  • name_registered — another process is already registered under name in that scope
  • reserved_name — name is in the OTP kernel / stdlib blocklist
  • type_error — name is not a Symbol, or scope is neither #local nor #global

Examples

leader := (Scheduler spawnAs: #scheduler scope: #global) unwrap
// on any node in the cluster:
s := (Scheduler named: #scheduler scope: #global) unwrap
spawnWith: initArgs as: name scope: scope Sealed source

Atomically spawn a new actor with init args and register it under name, in the requested cluster scope.

Same contract as spawnAs:scope: plus the initialisation arguments passed to the actor's init/1 callback. initArgs goes through the same wire encoder as every other cross-node argument (ADR 0126 §5.1) — no second encode path.

Examples

c := (Counter spawnWith: #{#count => 10} as: #counter scope: #global) unwrap
named: name scope: scope Sealed source

Look up an actor by name in the requested cluster scope, checked against the receiver class.

scope: #local behaves identically to named: (this node's local registry). scope: #global checks OTP's cluster-wide global registry instead — the same name resolves to the same actor from every connected node.

Error cases: as named:, plus type_error when scope is neither #local nor #global.

Examples

s := (Scheduler named: #scheduler scope: #global) unwrap
allRegistered Sealed source

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.

This lists only name-registered actors. To list every live actor (named or not) use Node current actors; every registered actor is also in that set.

Examples

Actor allRegistered  // => #(an Actor(Counter), an Actor(Logger))
spawnOn: node Sealed source

Spawn a new actor process on node, with default state.

Same contract as spawn, except the actor process starts on node instead of here — initialize, reserved-name checks, and ActorSpawned all fire there. See "Remote spawn and lookup" above for the idempotency and linking caveats.

Error cases:

  • node_down — node is unreachable
  • class_not_found — the class is not loaded on node

Examples

worker := (Node named: #'worker@localhost') unwrap
c := (Counter spawnOn: worker) unwrap
c node          // => Node(worker@localhost)
spawnAs: name on: node Sealed source

Atomically spawn a new actor on node and register it under name there.

Same contract as spawnAs:, except the actor process (and its registration) both happen on node instead of here. See "Remote spawn and lookup" above for the idempotency and linking caveats.

Error cases: as spawnAs:, plus node_down (node is unreachable) and class_not_found (the class is not loaded on node).

Examples

worker := (Node named: #'worker@localhost') unwrap
(Counter spawnAs: #hits on: worker) unwrap
spawnWith: initArgs on: node Sealed source

Spawn a new actor process on node, with initialization arguments.

Same contract as spawnWith:, except the actor process starts on node instead of here. initArgs is wire-encoded (ADR 0126 §5.1) before crossing the node boundary — a Value nested inside it is enveloped and version-checked like any other cross-node argument; a HandleScoped value (e.g. Ets) is rejected here, in the sender, before the erpc call. See "Remote spawn and lookup" above for the idempotency and linking caveats.

Error cases: as spawnOn:, plus not_serialisable (initArgs contains a node-scoped handle).

Examples

worker := (Node named: #'worker@localhost') unwrap
c := (Counter spawnWith: #{#count => 10} on: worker) unwrap
spawnWith: initArgs as: name on: node Sealed source

Atomically spawn a new actor with init args on node and register it under name there.

Same contract as spawnAs:on: plus the initialisation arguments passed to the actor's init/1 callback — see spawnWith:on: for the wire-encoding note.

Examples

worker := (Node named: #'worker@localhost') unwrap
(Counter spawnWith: #{#count => 10} as: #hits on: worker) unwrap
named: name on: node Sealed source

Look up a registered actor by name on node, checked against the receiver class.

Same contract as named:, checked against node's registry instead of this node's. The returned reference is node-qualified: it keeps resolving against node regardless of which node later sends to it.

Error cases: as named:, plus node_down (node is unreachable).

Examples

worker := (Node named: #'worker@localhost') unwrap
hits := (Counter named: #hits on: worker) unwrap
hits increment   // => 1 — sent to worker, not resolved locally
allRegisteredOn: node Sealed source

Return every currently-registered Beamtalk actor on node as Actor proxies.

Same contract as allRegistered, listing node's registered actors instead of this node's. Unlike the local, network-free allRegistered, this answers a Result — reaching another node is an expected-failure operation (ADR 0060).

Like allRegistered, this lists only name-registered actors. To list every live actor on node (named or not) use node actors.

Examples

worker := (Node named: #'worker@localhost') unwrap
(Actor allRegisteredOn: worker) unwrap  // => #(an Actor(Counter), ...)
supervisionPolicy source

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)
isSupervisor source

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)
supervisionSpec source

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

registerAs: name Sealed source

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 Sealed source

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
unregisterName source

Internal FFI seam (ADR 0101 Part 4): release this actor's registered name. Keeps unregister pure Beamtalk. Returns bare ok, which the FFI boundary auto-converts to Result ok: nil (ADR 0076/0121).

registeredName Sealed source

Return the Symbol this actor is registered under, or nil if unnamed.

Examples

counter registeredName   // => #counter (or nil)
isRegistered Sealed source

Whether this actor currently has a registered name.

Examples

counter isRegistered   // => true or false
withTimeout: ms source

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
initialize source

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 := #()
terminate: _reason source

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 on:

  • graceful shutdown (stop)
  • a supervisor's direct aSupervisor terminate: aClass
  • a supervisor-initiated shutdown of this actor as part of a rest_for_one / one_for_all restart cascade (this actor did not crash itself — a sibling did — but the supervisor tears it down too)

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:.

BT-3596: overriding terminate: makes this actor's class (and any subclass) trap exits (process_flag(trap_exit, true)), which is what lets OTP deliver a supervisor's shutdown signal as a message instead of killing the process before terminate: can run. A class that never overrides terminate: keeps the plain link semantics it always had.

Examples

Actor subclass: Logger
  state: logFile = nil
  terminate: reason =>
    self.logFile isNil ifFalse: [self.logFile close]
delegate Sealed source

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.

Deliberately left without a declared return type (BT-2862): the type checker special-cases an unannotated self delegate body to stay Dynamic, since a native override's real return type depends on whichever backing module it delegates to.

Examples

counter delegate   // => ERROR: delegate called on a non-native Actor
pid Sealed source

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
node source

The Node this actor lives on (ADR 0126 §2).

Answered by the actor itself, so a TimeoutProxy reports its target's node rather than its own (ADR 0126 §6).

Examples

counter node               // => Node(nonode@nohost)
counter node isCurrent     // => true
isRemote source

Whether this actor lives on a different node from the caller (ADR 0126 §2).

The runtime answers a send of isRemote in the caller's process — asking the actor for its node and comparing it with the caller's node — because the actor itself can only ever see its own node. This body only runs for a self-send, where the caller is the actor, so it is always false there.

Examples

counter isRemote           // => false
monitor Sealed source

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
onExit: block Sealed source

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}
]
stop Sealed source

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
kill Sealed source

Forcefully kill this actor (exit(Pid, kill)).

Unlike stop, kill cannot be trapped by the actor process.

Examples

counter kill   // => ok
isAlive Sealed source

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

class

Return the class of the receiver.

Examples

42 class              // => Integer
"hello" class         // => String
isNil

Test if the receiver is nil. Returns false for all objects except nil.

Examples

42 isNil              // => false
nil isNil             // => true
notNil

Test if the receiver is not nil. Returns true for all objects except nil.

Examples

42 notNil             // => true
nil notNil            // => false
ifNil: _nilBlock

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
ifNotNil: notNilBlock

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
ifNil: _nilBlock ifNotNil: notNilBlock

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
ifNotNil: notNilBlock ifNil: _nilBlock

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
printString

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"
displayString

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"
inspect

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)
yourself Sealed

Return the receiver itself. Useful for cascading side effects.

Examples

42 yourself            // => 42
hash

Return a hash value for the receiver.

Examples

42 hash
equals: other

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
respondsTo: selector Sealed

Test if the receiver responds to the given selector.

Examples

42 respondsTo: #abs    // => true
fieldNames Sealed

Return the names of fields.

Examples

42 fieldNames             // => #()
fieldAt: name Sealed

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
fieldAt: name put: value Sealed

Set the value of the named field (returns new state).

Examples

object fieldAt: #name put: "Alice"
hasField: name Sealed

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
clearField: name Sealed

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
perform: selector Sealed

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
perform: selector withArguments: args Sealed

Send a message dynamically with arguments.

See perform: above for why no return type is declared.

Examples

3 perform: #max: withArguments: #(5)   // => 5
subclassResponsibility

Raise an error indicating this method must be overridden by a subclass.

Examples

self subclassResponsibility
notImplemented

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
show: aValue

Send aValue to Transcript without a trailing newline.

Outside an interactive workspace this is a Logger notice (ADR 0129 §5).

Examples

42 show: "value: "
showCr: aValue

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"
isKindOf: aClass

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)
error: message

Raise an error with the given message.

Examples

self error: "something went wrong"
delegate Sealed

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

== other

Test value equality (Erlang ==, non-strict — 1 == 1.0 is true).

Examples

42 == 42           // => true
"abc" == "abc"     // => true
/= other

Test value inequality (negation of ==).

Examples

1 /= 2             // => true
42 /= 42           // => false
=:= other

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
=/= other

Test strict value inequality (negation of =:=).

Examples

1 =/= 1.0           // => true (strict — different types)
1 =/= 1             // => false
class

Return the class of the receiver.

Examples

42 class            // => Integer
"hello" class       // => String
doesNotUnderstand: selector args: arguments

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
perform: selector withArguments: arguments

Send a message dynamically with an arguments list.

Examples

42 perform: #abs withArguments: #()   // => 42
performLocally: selector withArguments: arguments

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)
perform: selector withArguments: arguments timeout: timeoutMs

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