A live, interactive Smalltalk-like language for the BEAM VM
Beamtalk brings Smalltalk's legendary live programming experience to Erlang's battle-tested runtime. While inspired by Smalltalk's syntax and philosophy, Beamtalk makes pragmatic choices for modern development (see Syntax Rationale). Write code in a running system, hot-reload modules without restarts, and scale to millions of concurrent actors.
// Spawn an actor with state
counter := Counter spawn
// Send messages (sync by default)
counter increment
counter increment
value := counter getValue // => 2
// Send an async message
counter increment! // => ok
// Cascades - multiple messages to same receiver
Transcript show: "Hello"; cr; show: "World"
// Map literals
config := #{#host => "localhost", #port => 8080}
| Feature | Benefit |
|---|---|
| Interactive-first | REPL and live workspace, not batch compilation |
| Hot code reload | Edit and reload modules in running systems |
| Actor model | Actors are BEAM processes with independent fault isolation |
| Gradual typing | Opt-in typed classes with inference through generics, unions, FFI, and narrowing |
| Reflection | Inspect any actor's state and methods at runtime |
| Runs on BEAM | Compiles to Core Erlang; deploy to existing OTP infrastructure |
| Native Erlang FFI | Call any OTP function with typed specs and Result-shaped error coercion |
| Package manager | Path / git / hex deps with lockfile, qualified pkg@Class names |
| Testing built-in | SUnit-style TestCase framework with beamtalk test |
- Type system grows teeth. Mark a class
typedand the compiler flows types through generics, unions, narrowing predicates, FFI calls, and method-local type variables.beamtalk type-coveragereports per-file coverage, and the LSP "Dynamic with reason" hover shows why a chain decayed. - Real package manager.
[dependencies]inbeamtalk.toml(path / git / hex),beamtalk.lockfor reproducibility,beamtalk deps add, and qualifiedpkg@Classnames with cross-package collision detection. - Native Erlang inside packages. Drop
.erlfiles innative/and they compile via a vendoredrebar3;beamtalk testruns bothBUnitandEUnit;:h Erlang <module>hovers on user-defined modules. - Result-shaped FFI. Erlang functions specced as
{ok, T} | {error, R}are coerced to typedResult(T, E)at the boundary (ADR 0076). - Result-shaped supervisors.
Supervisor>>supervise / terminate: / which:andDynamicSupervisor>>startChildreturnResultinstead of raising, with idempotent-startup semantics (ADR 0080). - Named actor registration.
Class spawnAs: name,Class named: name, supervised re-registration on restart (ADR 0079). internalmodifier for package-scoped access control (ADR 0071).- Local variable type annotations —
name :: Type := expr. - Auto-chained
initializeacross the actor hierarchy (ADR 0078). - REPL
:syncsupersedes:load/:reloadfor project-wide reloads;:interruptcancels a running eval out-of-band; parallel:test. - Live-edit save model —
Counter >> sel => bodyandCounter compile: #sel source: bodypatch memory and append to a workspaceChangeLog;Workspace flush(REPL:flush, MCPflush, LSPbeamtalk.flush*) splices the patched body back into the.btfile via byte-span replacement, atomically, with external-edit conflict detection.:dirty/:changesshow pending state;Workspace autoflush: trueopts into write-through (ADR 0082). - Class crash recovery via ETS
heirso live actors keep dispatching after a class process restart.
See CHANGELOG.md for the full list.
Every Beamtalk actor is a BEAM process with its own state and mailbox:
Actor subclass: Counter
state: value = 0
increment => self.value := self.value + 1
decrement => self.value := self.value - 1
getValue => self.value
Match expressions with pattern arms:
status match: [
#ok -> "success"
#error -> "failure"
_ -> "unknown"
]
// Variable binding in patterns
42 match: [n -> n + 1] // => 43
Rich collection types written in Beamtalk itself:
list := #(1, 2, 3)
list collect: [:x | x * 2] // => #(2, 4, 6)
dict := #{#name => "Alice", #age => 30}
dict at: #name // => Alice
Redefine methods on running actors — state is preserved:
// In the REPL: redefine a method on a running class
Counter >> increment => self.value := self.value + 10
// Existing actors immediately use the new code
c increment // now adds 10 instead of 1
The quickest way to get started — downloads a prebuilt binary for your platform:
curl -fsSL https://jamesc.github.io/beamtalk/install.sh | shThis installs to ~/.beamtalk/bin/. You can customise the location:
curl -fsSL https://jamesc.github.io/beamtalk/install.sh | sh -s -- --prefix /usr/localPrerequisite: Erlang/OTP 27+ must be installed with erl on PATH.
Installing Erlang/OTP
macOS (Homebrew):
brew install erlangUbuntu/Debian:
sudo apt install erlangWindows: Download the installer from erlang.org.
Version manager (any platform): asdf with asdf-erlang:
asdf plugin add erlang
asdf install erlang 27.0
asdf global erlang 27.0After installation, start the REPL:
beamtalk replPrerequisites for building from source
- Rust (latest stable) — rustup.rs
- Erlang/OTP 27+ with
erlanderlcon PATH - rebar3 — Erlang build tool (rebar3.org)
- Just — command runner (
cargo install just) - Node.js LTS (optional) — only for building the VS Code extension
# Clone and build
git clone https://github.com/jamesc/beamtalk.git
cd beamtalk
just build
# Start the REPL
just repl
# Run tests
beamtalk test # Run BUnit TestCase tests
just test-stdlib # Run compiled expression tests
just test-repl-protocol # Run REPL TCP-protocol tests
# Run full CI checks
just ci# Install to /usr/local (may need sudo)
just install
# Install to a custom prefix
just install PREFIX=$HOME/.local
# Uninstall
just uninstall PREFIX=$HOME/.localThe install layout follows the OTP convention (PREFIX/lib/beamtalk/lib/<app>/ebin/), so beamtalk repl and beamtalk build work correctly from any directory when the binary is on PATH.
After installing, verify your environment:
beamtalk doctorFor local extension development (debug LSP, no .vsix packaging):
just build-vscodeThen in VS Code, run Developer: Install Extension from Location... and select editors/vscode.
If your stdlib source files are outside the project root, set:
{
"beamtalk.stdlib.sourceDir": "/opt/beamtalk/stdlib/lib"
}beamtalk.stdlib.sourceDir can be absolute (including outside the project) or relative to the Beamtalk project root (directory containing beamtalk.toml). See editors/vscode/README.md for full extension configuration.
New to Beamtalk? See the REPL Tutorial for a complete beginner's guide!
Beamtalk v0.4.0
Type :help for available commands, :exit to quit.
> message := "Hello, Beamtalk!"
"Hello, Beamtalk!"
> 2 + 3 * 4
14
> :load "examples/getting-started/src/hello.bt"
nil
> Hello new greeting
│ Hello, World!
a Hello
> :load "examples/getting-started/src/counter.bt"
nil
- Docker Desktop — For devcontainer support
- VS Code — With the Dev Containers extension
- Rust (latest stable) — For building the compiler
- Erlang/OTP 27+ — With
erlcon PATH
- Node.js LTS + npm — For building the VS Code extension (
editors/vscode/)
| Variable | Purpose | How to Set |
|---|---|---|
GH_TOKEN |
GitHub authentication for devcontainers | gh auth login, then $env:GH_TOKEN = (gh auth token) |
LINEAR_API_TOKEN |
Linear issue tracking | Get from Linear API settings |
VS Code (Recommended):
- Clone the repository
- Open in VS Code: File → Open Folder
- When prompted, click Reopen in Container
- Wait for container build (~5 minutes first time)
- Open a terminal and run:
just build - Enable the pre-push lint hook:
git config core.hooksPath .githooks
The devcontainer includes all dependencies pre-configured:
- Rust toolchain with
clippy,rustfmt,rust-analyzer - Erlang/OTP 27+ and
rebar3 - Node.js LTS for build tooling
- GitHub CLI (
gh) with authentication - GitHub Copilot CLI for AI assistance
- VS Code extensions for Rust, TOML, Erlang, GitHub, Linear
The devcontainer uses GH_TOKEN for GitHub CLI authentication:
# Verify authentication (inside container)
gh auth status
# Login manually if needed
gh auth loginPriority order:
GH_TOKENenvironment variable (from host)- VS Code credential helper (auto-configured)
- Manual
gh auth login
Set your identity for commits:
Option 1: Environment variables
export GIT_USER_NAME="Your Name"
export GIT_USER_EMAIL="you@example.com"Option 2: Global git config (inside container)
git config --global user.name "Your Name"
git config --global user.email "you@example.com"The postStartCommand in devcontainer.json automatically configures git from environment variables.
Configure SSH signing for verified commits:
1. Generate SSH signing key (on host):
ssh-keygen -t ed25519 -C "you@example.com" -f ~/.ssh/id_ed25519_signing2. Add public key to GitHub:
cat ~/.ssh/id_ed25519_signing.pub
# Copy output to GitHub Settings → SSH and GPG keys → New SSH key (select "Signing Key")3. Configure git (inside container):
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519_signing
git config --global commit.gpgsign true4. Add private key to SSH agent:
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519_signing| Tool | Version | Purpose |
|---|---|---|
rustc |
Latest stable | Rust compiler |
cargo |
Latest | Rust package manager |
clippy |
Latest | Rust linter |
rustfmt |
Latest | Rust code formatter |
just |
Latest | Task runner (alternative to Make) |
erlc |
OTP 27+ | Erlang compiler |
erl |
OTP 27+ | Erlang runtime |
rebar3 |
Latest | Erlang build tool |
gh |
Latest | GitHub CLI |
copilot |
Latest | GitHub Copilot CLI (AI assistant) |
node |
LTS | Node.js runtime |
npm |
Latest | Node package manager |
Pre-installed via cargo-binstall:
cargo-watch— Auto-rebuild on file changescargo-nextest— Faster test runnercargo-insta— Snapshot testingcargo-llvm-cov— Code coverage reports
Automatically installed in devcontainer:
rust-lang.rust-analyzer— Rust language servertamasfe.even-better-toml— TOML file supportvadimcn.vscode-lldb— Debugger for Rustusernamehw.errorlens— Inline error displayerlang-ls.erlang-ls— Erlang language servergithub.copilot— AI pair programminggithub.vscode-github-actions— GitHub Actions integrationgithub.vscode-pull-request-github— PR managementlinear.linear— Linear issue tracking
Active development — the compiler core is working with an interactive REPL.
- ✅ REPL — Interactive evaluation with variable persistence,
:sync,:interrupt, parallel:test - ✅ Lexer & Parser — Full expression parsing with error recovery
- ✅ Core Erlang codegen — Compiles to BEAM bytecode via
erlc - ✅ Actors & Supervision — Spawn actors with state, sync/async messages, futures, OTP supervision with Result-shaped lifecycle, named registration
- ✅ Field assignments — Actor state mutations via
:= - ✅ Method dispatch — Full message routing (unary, binary, keyword)
- ✅ Pattern matching —
match:expressions with literal, variable, and constructor patterns - ✅ Hot code reloading — Redefine classes/methods on running actors via
>>; class crash recovery preserves dispatch tables - ✅ Type system —
typedclasses, generics, protocols, unions, control-flow narrowing, FFI inference,Never,Self class, local annotations - ✅ Package manager —
beamtalk.toml[dependencies](path/git/hex),beamtalk.lock, qualifiedpkg@Classnames,beamtalk deps - ✅ Native Erlang in packages —
native/*.erlcompiled via vendored rebar3;EUnitruns throughbeamtalk test - ✅ Standard library — Boolean, Block, Integer, Float, String, Character, Collections, Result, Printable, Package, Tracing
- ✅ Class system — Inheritance,
super,sealed,internalaccess control, class-side methods, abstract classes, auto-chainedinitialize - ✅ Cascades, map literals, comprehensions, exception handling
- ✅ LSP — Completions (incl. FFI), hover with type/Dynamic-reason, go-to-definition, find references, workspace symbols, diagnostics
- ✅ Testing — SUnit-style
TestCase, parallel runner,BUnit+EUnitintegration - ✅ Tooling —
beamtalk doctor,beamtalk type-coverage,beamtalk gen-native,beamtalk generate stubs, MCP server, VS Code extension
- 📋 Live browser — Smalltalk-style class browser (Phoenix
LiveView)
📚 Documentation Index — Start here for a guided tour 🌐 API Reference — Standard library API docs (auto-generated) 📖 Documentation Site — Full docs including language features, principles, and architecture
- Design Principles — 13 core principles guiding all decisions
- Language Features — Syntax, semantics, and examples
- Syntax Rationale — Why we keep/change Smalltalk conventions
- Object Model — How Smalltalk objects map to BEAM
- Known Limitations — Current limitations
- Architecture — Compiler pipeline, runtime, hot reload
- Testing Strategy — How we verify compiler correctness
- Agent-Native Development — AI agents as developers and live actor systems
Examples (examples/)
Simple programs demonstrating language features:
cargo run -- repl
> :load "examples/getting-started/src/hello.bt"Standard Library (stdlib/src/)
Foundational classes implementing "everything is a message":
| Class | Description |
|---|---|
Actor |
Base class for all actors |
Block |
First-class closures |
True / False |
Boolean control flow |
Integer / Float |
Numeric types |
String / Character |
UTF-8 text and characters |
List / Tuple |
Ordered collections |
Set / Dictionary |
Unordered collections |
Nil |
Null object pattern |
TestCase |
SUnit-style test framework |
See stdlib/src/README.md for full documentation.
beamtalk/
├── crates/
│ ├── beamtalk-core/ # Lexer, parser, AST, codegen, type checker
│ ├── beamtalk-cli/ # Command-line interface & REPL
│ ├── beamtalk-lsp/ # Language server (LSP)
│ └── beamtalk-repl-protocol/ # Shared REPL response types
├── stdlib/src/ # Standard library (.bt files)
├── runtime/ # Erlang runtime (actors, REPL backend)
├── stdlib/test/ # BUnit test cases (TestCase classes)
├── tests/
│ ├── repl-protocol/ # REPL TCP-protocol tests
│ └── parity/ # Cross-surface parity tests
├── docs/ # Design documents & ADRs
├── examples/ # Example programs
└── editors/vscode/ # VS Code extension
The compiler is written in Rust and generates Core Erlang, which compiles to BEAM bytecode via erlc.
Beamtalk combines ideas from:
- Smalltalk/Newspeak — Live programming, message-based syntax, reflection (inspiration, not strict compatibility)
- Erlang/BEAM — Actors, fault tolerance, hot code reload, distribution
- Elixir — Protocols, comprehensions, with blocks
- Gleam — Result types, exhaustive pattern matching
- Dylan — Sealing, conditions/restarts, method combinations
- TypeScript — Compiler-as-language-service architecture
We welcome contributions! See CONTRIBUTING.md for how to get started — covering dev setup, running tests, PR guidelines, and where to help.
For AI agent contributors, see AGENTS.md for detailed development guidelines.
We use Linear for issue tracking (project prefix: BT).
Licensed under the Apache License, Version 2.0. See LICENSE for details.