Public names for the sealed raw carriers used by this module's signatures. These aliases make the Std.Otp interface self-contained without leaking every bare name imported from Std.Otp.Raw into downstream modules.

Types

  • type Plain
  • type Server
  • type RawPid
  • type RawServerPid
  • type RawDepServerPid
  • type RawFsmPid
  • type RawSupervisorPid
  • type NoMessage
  • type BarePid
  • type Ref
  • type MonitorRef
  • type TimerRef
  • type RawTerm
  • type Selector
  • type Pid

    The typed BEAM process algebra — the sanctioned concurrency surface.

    Two typed handles, both opaque phantoms erased to a raw erlang:pid() at runtime; their whole job is static — using a process at the WRONG message or reply type is a COMPILE error, the guarantee Std.Otp.Raw's untyped Pid cannot give (design 2026-07-10-checked-beam-concurrency §2, 2026-07-09-typed-beam-process-algebra):

    • Pid(m) — a plain process accepting messages of type m (raw erlang:send, one-way).
    • GenServer(q, r) — a gen_server taking requests q; a synchronous call returns a reply r. The reply type rides on the SERVER'S type, so call needs no return-only implicit (which does not resolve through an effectful extern) — a wrong request AND a wrong reply are both caught.

    Every operation performs a BEAM side effect, so it returns Effect(T). The effect discipline forbids duplicating, dropping, or reordering an OPERATION — a statement about the PROGRAM'S EFFECT SEQUENCE, not about the mailbox. The BEAM promises no delivery at all (a tell to a dead process silently succeeds) and orders signals only PAIRWISE, from one sender to one target; nothing in this module claims more. Messages are POLYMORPHIC (the narrow point for a message set), never Any.

    The typed layer calls the raw externs through ordinary qualified Cure definitions. The only foreign declarations are in Std.Otp.Raw; the imported opaque carrier preserves erased runtime identity while the public aliases below attach message and request/reply indices. A typed handle to a plain BEAM process accepting messages of type m. It answers raw sends, and nothing else.

  • type GenServer

    A typed gen_server taking requests q, replying r. DISTINCT from Pid(m): call/cast/stop are gen_server protocol operations, and a plain spawned process cannot answer them — it would block the caller for 5s and then exit it. The Plain / Server tag is a phantom at kind Type: no values, no runtime cost.

  • type ActorServer

    An actor-like server has independent asynchronous and synchronous input codes. All three indices are erased; the runtime value is the native pid.

  • type FsmPid

    Typed handle for a generated finite-state machine. state and data are phantom observations; event is the only asynchronously accepted code.

  • type SupervisorHandle

    Typed handle to an OTP supervisor. The carrier is a bare native pid, but it is not interchangeable with an actor, FSM, or freely sendable process.

  • type StartResult = Started | StartFailed | StartIgnored | InvalidStartResult
  • type DepActorServer
  • type Reply

    The callback-side one-shot reply capability supplied by gen_server. It erases to OTP's {pid, tag} tuple but remains linear at Cure call sites.

  • type Subject

    A typed message ADDRESS — the Gleam-style Subject. A fresh (owner-pid, unique-tag) pair; UNLIKE Pid(m) (one message type per process) a single process may own SEVERAL subjects of different types, each a distinct typed channel. subject_send delivers a TAGGED message to the owner; subject_receive matches that tag, so channels never cross. Erases to the bare {pid, ref} tuple — no registry / persistent_term (AtomVM-safe). This is the addressing layer Selector (typed selective receive) and typed Name build on.

  • type MessageCode = Empty | Shape

    A process message set. The first algebra floor stores tag and arity shapes; typed ADT payload derivation is added by the transparent macro phase once callback syntax is available.

  • type BeamTerm

    Typed lifecycle and observation wrappers over the raw effect boundary. MonitorRef and TimerRef are the raw base's own DISTINCT carriers (imported, not aliased): they used to be two names for one Ref, which made cancel_timer(monitor_ref) well-typed.

Functions

  • # fn <-|(m: Type, r: Type, k: Type, dest: RawPid(m, r, k), msg: m) -> Effect(Unit)

    Melquiades is ordinary operator sugar over the checked operation above. Both spellings have exactly tell's indexed message check and effect result; neither restores the retired raw-process send node.

  • # fn actor_call(m: Type, q: Type, r: Type, server: ActorServer(m, q, r), request: q) -> Effect(r)
  • # fn actor_call_dep(m: Type, q: Type, rep: Function(q, Type), server: DepActorServer(m, q, rep), request: q) -> Effect(rep(request))
  • # fn actor_cast(m: Type, q: Type, r: Type, server: ActorServer(m, q, r), message: m) -> Effect(Unit)
  • # fn actor_stop(m: Type, q: Type, r: Type, server: ActorServer(m, q, r)) -> Effect(Unit)
  • # fn as_dep(q: Type, rep: Function(q, Type), server: GenServer(q, Unit)) -> DepGenServer(q, rep)

    View a running gen_server as a dependent server under a chosen reply family rep (from the expected result type). Zero runtime content beyond the wrapped pid.

  • # fn as_dep_actor(m: Type, q: Type, rep: Function(q, Type), server: ActorServer(m, q, Unit)) -> DepActorServer(m, q, rep)
  • # fn call(q: Type, r: Type, server: GenServer(q, r), request: q) -> Effect(r)

    Synchronous gen_server call: send request q, block for reply r. Both the request AND the reply are checked against the server's type.

    ⚠ PARTIAL. On timeout (default 5000 ms) or server death the CALLER EXITS. Effect(r) is sound — no value is ever returned at the wrong type — but it is NOT total: there is simply no continuation. A try_call reifying the failure as a value needs a try/catch shim and is deferred (audit §6).

  • # fn call_dep(q: Type, rep: Function(q, Type), server: DepGenServer(q, rep), request: q) -> Effect(rep(request))
  • # fn cancel_timer(ref: TimerRef) -> Effect(Option(Int))

    Cancel a timer, surfacing what the BIF actually reports: Some(ms) when the timer was still pending (ms milliseconds were left on it), None when it had already fired or been cancelled. The old Effect(Unit) threw that answer away, which is how a caller could believe a cancellation succeeded when it had not.

    ⚠ AtomVM: erlang:send_after/3 registers its timer under a DIFFERENT ref than the one it returns (timer_manager.erl:87-91), so on AtomVM cancelling a send_after ref always yields None AND THE MESSAGE STILL FIRES. Cancellation is reliable on OTP; on AtomVM it is not. See the audit, §5.

  • # fn cast(q: Type, r: Type, server: GenServer(q, r), request: q) -> Effect(Unit)

    Asynchronous gen_server cast — a typed fire-and-forget request (no reply). gen_server:cast/2 answers the constant atom ok; the wrapper discards it.

  • # fn demonitor(ref: MonitorRef) -> Effect(Unit)

    Remove a monitor AND flush any DOWN already sitting in the mailbox. Without flush a stale DOWN outlives the call, and every receiving match has to carry an arm for a monitor the caller believes it already removed.

  • # fn dep_actor_cast(m: Type, q: Type, rep: Function(q, Type), server: DepActorServer(m, q, rep), message: m) -> Effect(Unit)
  • # fn dep_actor_stop(m: Type, q: Type, rep: Function(q, Type), server: DepActorServer(m, q, rep)) -> Effect(Unit)
  • # fn exit(m: Type, r: Type, k: Type, pid: RawPid(m, r, k), reason: ExitReason) -> Effect(Unit)

    Send an exit signal. Makes NO type-level claim that the target dies — correctly: which of the three outcomes occurs depends on the target's trap_exit flag, which the sender cannot see. A polymorphic reason could not even state the distinction.

  • # fn fsm_send(event: Type, state: Type, data: Type, machine: FsmPid(event, state, data), value: event) -> Effect(Unit)
  • # fn handles(code: MessageCode, tag: Atom, arity: Int) -> Bool

    Does code accept a message tag with the given payload arity?

  • # fn is_alive(m: Type, r: Type, k: Type, pid: RawPid(m, r, k)) -> Effect(Bool)
  • # fn map_start(a: Type, b: Type, result: StartResult(a), convert: Function(a, b)) -> StartResult(b)
  • # fn message_code(tag: Atom, arity: Int) -> MessageCode

    A single message shape constructor, convenient for macro-generated codes.

  • # fn monitor(m: Type, r: Type, k: Type, kind: MonitorKind, pid: RawPid(m, r, k)) -> Effect(MonitorRef)
  • # fn name(m: Type, registered: Atom) -> Name(m)

    A typed name over a registered atom. m asserts what the named process accepts.

  • # fn new_selector(p: Type) -> Selector(p)

    Typed SELECTIVE RECEIVE (Gleam-style Selector). Accumulate handlers over several subjects of DIFFERENT message types — each mapped into a common payload p by select_map — then selector_receive returns whichever arrives first, typed as p. select is the identity case (the subject's messages ARE the payload). Building a selector is pure; only the receive is an Effect.

  • # fn new_subject(m: Type) -> Effect(Subject(m))

    Mint a fresh subject owned by the calling process.

  • # fn register(m: Type, r: Type, k: Type, name: Atom, pid: RawPid(m, r, k)) -> Effect(Unit)
  • # fn register_name(m: Type, n: Name(m), pid: Pid(m)) -> Effect(Bool)

    Register a Pid(m) under the name. Returns whether registration succeeded (false if the name is taken).

  • # fn reply(r: Type, from: Reply(r), value: r) -> Effect(Unit)

    Send one reply through a callback's linear from capability. Generated actor callbacks use this direct operation and return noreply.

  • # fn select(p: Type, selector: Selector(p), subject: Subject(p)) -> Selector(p)

    Add a subject whose message type IS the payload (identity mapping).

  • # fn select_map(p: Type, m: Type, selector: Selector(p), subject: Subject(m), transform: Function(m, p)) -> Selector(p)

    Add subject, mapping its messages into the selector's payload p.

  • # fn selector_receive(p: Type, selector: Selector(p), timeout: Int) -> Effect(Option(p))

    Receive from whichever selected subject arrives first within timeout ms; None on timeout.

  • # fn self(m: Type) -> Effect(Pid(m))

    The calling process's own typed pid; the erased message index is supplied by the enclosing expected result at each use site.

  • # fn send_after(m: Type, r: Type, k: Type, delay: Int, pid: RawPid(m, r, k), msg: m) -> Effect(TimerRef)

    The signal-layer operations below accept BOTH handles: linking, monitoring, exiting, naming and timing are properties of a BEAM PROCESS, not of the protocol it speaks. They are tag-polymorphic in k.

  • # fn spawn(m: Type, thunk: Function(Unit)) -> Effect(Pid(m))

    Spawn a nullary process function and return its indexed handle.

  • # fn start_actor(a: Type, m: Type, q: Type, r: Type, module: Atom, args: a) -> Effect(StartResult(ActorServer(m, q, r)))

    Start a checked actor callback module and decode OTP's heterogeneous result. Only the :ok branch may attach actor protocol indices, and only after the second tuple element has passed the native PID guard.

  • # fn start_fsm(a: Type, event: Type, state: Type, data: Type, module: Atom, args: a) -> Effect(StartResult(FsmPid(event, state, data)))
  • # fn start_statem(a: Type, module: Atom, args: a) -> Effect(Tuple)
  • # fn start_supervisor(module: Atom) -> Effect(Tuple)
  • # fn start_supervisor_with(a: Type, module: Atom, args: List(a)) -> Effect(Tuple)
  • # fn start_typed_supervisor(module: Atom) -> Effect(StartResult(SupervisorHandle))
  • # fn stop(q: Type, r: Type, server: GenServer(q, r)) -> Effect(Unit)

    Stop a gen_server. Server-only — gen_server:stop on a plain process blocks and then exits the caller. The BIF answers the constant atom ok; discarded.

  • # fn stop_supervisor(supervisor: SupervisorHandle, reason: ExitReason) -> Effect(Unit)
  • # fn subject_receive(m: Type, subject: Subject(m), timeout: Int) -> Effect(Option(m))

    Receive the next message addressed to subject within timeout ms; None on timeout. Only the subject's OWNER (the process that created it) may receive.

  • # fn subject_send(m: Type, subject: Subject(m), message: m) -> Effect(Unit)

    Send a typed message to a subject — delivered TAGGED to the owner's mailbox (fire-and-forget, like tell).

  • # fn subset(narrow: MessageCode, wide: MessageCode) -> Bool

    Is every shape in narrow also accepted by wide?

  • # fn tell(m: Type, r: Type, k: Type, dest: RawPid(m, r, k), msg: m) -> Effect(Unit)

    Send a well-typed message: msg : m must match dest's accepted type m, so a Pid(Command) can never be sent a Telemetry. (Named tell, not send: the Melquiades operators below provide the compact send surface.)

    Accepts BOTH handles: a raw send to a gen_server is legitimate BEAM practice — it lands in handle_info. The raw BIF returns the message itself; the typed wrapper DISCARDS it — a send is fire-and-forget, and echoing the argument back tells the caller nothing. Discarding is a choice the typed layer makes explicitly, not a lie the raw type tells.

  • # fn union(left: MessageCode, right: MessageCode) -> MessageCode

    Union two codes, preserving the first code's order and removing duplicate tag/arity shapes from the second code.

  • # fn unregister(name: Atom) -> Effect(Unit)
  • # fn unregister_name(m: Type, n: Name(m)) -> Effect(Bool)

    Remove the name's registration. Returns whether it was registered.

  • # fn whereis(name: Atom) -> Effect(Option(BarePid))

    Look up a registered name. None when the name is not registered — the BIF returns the atom undefined there, and the old signature asserted a pid.

    The result is a BarePid: you may link, monitor, exit or is_alive it, but you cannot tell to it. Nothing associates a registered NAME with a message TYPE, so a typed handle cannot be recovered from one (audit F-1 / parent spec §13.7).

  • # fn whereis_name(m: Type, n: Name(m)) -> Effect(Option(Pid(m)))

    Look up the process registered under the name as a TYPED, sendable Pid(m); None if unregistered.

  • # fn (m: Type, r: Type, k: Type, dest: RawPid(m, r, k), msg: m) -> Effect(Unit)