Welcome to Beamtalk! This tutorial will guide you through using the interactive REPL to explore the language.
beamtalk replYou should see:
Beamtalk v0.4.0
Type :help for available commands, :exit to quit.
>
Try some simple expressions:
> 2 + 2
4
> 10 * 5
50
> "Hello, " ++ "Beamtalk!"
Hello, Beamtalk!
Assign values to variables:
> x := 42
42
> y := x + 10
52
> name := "Alice"
Alice
Note: In Beamtalk, := is used for assignment (not =).
Beamtalk strings support interpolation with {expr} — embed any expression inside a string:
> name := "Alice"
Alice
> "Hello, {name}!"
Hello, Alice!
> "2 + 2 = {2 + 2}"
2 + 2 = 4
> "{name length} characters"
5 characters
Non-string values are automatically converted via printString. To include literal braces in an interpolated string, prefix the brace characters with a backslash (write \{ and \} inside the string):
> "Set: \{1, 2, 3\}"
Set: {1, 2, 3}
See all your current variable bindings:
> :bindings
Variable bindings:
x = 42
y = 52
name = "Alice"
Load a Beamtalk source file and compile it as a module:
> :load examples/getting-started/src/hello.bt
Loaded Hello
Note: :load is for loading class definitions. The class name in the file determines the module name, not the filename.
After loading, you can see loaded classes:
> Workspace classes
#(Hello)
Reload after editing:
> :reload
Reloaded examples/getting-started/src/hello.bt
Beamtalk is built on the actor model. Actors are objects that run concurrently and communicate via messages.
Note: To try the examples below, you'll first need to load an actor class. The Counter actor is available in the examples:
> :load examples/getting-started/src/counter.bt
Loaded examples/getting-started/src/counter.bt
Now you can create and interact with Counter actors:
Create a new actor instance:
> myCounter := Counter spawn
#Actor<Counter, pid=<0.123.0>>
Send messages to actors:
> myCounter increment
1
> myCounter increment
2
> myCounter getValue
2
Auto-await: Message sends return Futures, but the REPL automatically awaits them for a synchronous experience.
Blocks are Beamtalk's closures:
> double := [ :x | x * 2 ]
#Block
> double value: 21
42
> numbers := #(1, 2, 3, 4, 5)
#(1, 2, 3, 4, 5)
> numbers collect: [ :n | n * n ]
#(1, 4, 9, 16, 25)
Beamtalk uses message sends for control flow:
> x := 5
5
> x > 10 ifTrue: [ "big" ] ifFalse: [ "small" ]
small
> count := 0
0
> [ count < 5 ] whileTrue: [ count := count + 1 ]
5
Use :help to see all available commands:
> :help
Beamtalk REPL Commands:
:help, :h Show this help message
:help <Class> Show instance-side class docs and methods
:help <Class> <sel> Show instance-side method documentation
:exit, :q Exit the REPL
:clear Clear all variable bindings
:bindings Show current variable bindings
:load <path> Load a .bt file or directory
:reload Reload the last loaded file or directory
:reload <Class> Reload a class by name
:unload <Class> Unload a class from the workspace
:test Run all test classes
:test <Class> Run a test class
:show-codegen <expr> Show generated Core Erlang for an expression
:sc <expr> Short alias for :show-codegen
> x := 42
42
> :clear
✓ All bindings cleared
> x
Error: Undefined variable: x
> Workspace classes
#(Counter)
> Workspace testClasses
#()
After editing a file:
> :reload
✓ Reloaded examples/getting-started/src/counter.bt
Beamtalk has a Stream class for lazy, composable data processing. Operations build up a pipeline — nothing computes until you use a terminal operation like take: or asList.
> (Stream from: 1) take: 5
[1,2,3,4,5]
> (Stream from: 1 by: [:n | n * 2]) take: 6
[1,2,4,8,16,32]
Chain lazy operations to build a pipeline, then pull results with a terminal:
> evens := (Stream from: 1) select: [:n | n isEven]
a Stream
> evens take: 5
[2,4,6,8,10]
> squares := evens collect: [:n | n * n]
a Stream
> squares take: 4
[4,16,36,64]
Any collection responds to stream for lazy processing:
> (#(1, 2, 3, 4, 5) stream) select: [:n | n > 2]
a Stream
> ((#(1, 2, 3, 4, 5) stream) select: [:n | n > 2]) asList
[3,4,5]
> ("hello" stream) take: 3
["h","e","l"]
Eager vs Lazy: Collection methods like List select: return a List immediately. Using stream first makes it lazy — you choose:
> #(1, 2, 3) select: [:n | n > 1]
[2,3]
> (#(1, 2, 3) stream) select: [:n | n > 1]
a Stream
Read files lazily — constant memory, safe for large files:
> (File lines: "examples/getting-started/src/hello.bt") take: 3
["// Copyright 2026 James Casey","// SPDX-License-Identifier: Apache-2.0",""]
> (File lines: "examples/getting-started/src/hello.bt") inject: 0 into: [:count :line | count + 1]
15
Block-scoped handles close automatically:
> (File open: "examples/getting-started/src/hello.bt" do: [:handle | handle lines take: 2]) unwrap
["// Copyright 2026 James Casey","// SPDX-License-Identifier: Apache-2.0"]
Note: File-backed Streams must be consumed by the same process that created them. To send file data to an actor, materialize with take: or asList first.
printString shows the pipeline structure without evaluating it:
> (Stream from: 1) printString
Stream(from: 1)
> ((Stream from: 1) select: [:n | n isEven]) printString
Stream(from: 1) | select: [...]
| Method | Description |
|---|---|
take: |
First N elements as List |
asList |
Materialize entire stream |
do: |
Iterate with side effects |
inject:into: |
Fold/reduce |
detect: |
First match or nil |
anySatisfy: |
Any element matches? |
allSatisfy: |
All elements match? |
stream collect: [:x | Transcript show: x. x * 2], the show: only runs when a terminal operation pulls elements through — not when the pipeline is defined.
The REPL provides convenience bindings for workspace singletons. You can use either the short binding name or the explicit class method:
// Convenience binding (available in REPL)
> Transcript show: "hello"
hello
// Explicit form (works everywhere, including loaded classes)
> TranscriptStream current show: "hello"
hello
Both forms work in the REPL. In loaded classes (.bt files), use the explicit form since convenience bindings are only available in REPL sessions.
| Convenience Binding | Explicit Form | Description |
|---|---|---|
Transcript |
TranscriptStream current |
Output stream for debugging |
Beamtalk |
BeamtalkInterface current |
System dictionary |
Workspace |
WorkspaceInterface current |
Actor introspection |
> :exit
Goodbye!
Or use :q as a shortcut.
- Tab completion - Press Tab to complete variable names and commands
- History - Use ↑/↓ arrow keys to navigate command history
- Multi-line input - Use Shift+Enter for multi-line expressions (coming soon)
- Error messages - Errors show source location and suggestions
- Auto-await - Message sends are automatically awaited for convenience
Here's a complete example session exploring basic expressions:
$ beamtalk repl
Beamtalk v0.4.0
Type :help for available commands, :exit to quit.
> x := 42
42
> y := x + 10
52
> z := x * y
2184
> :bindings
Variable bindings:
x = 42
y = 52
z = 2184
> :load examples/getting-started/src/hello.bt
Loaded Hello
> Workspace classes
#(Hello)
> :exit
Goodbye!
- Explore the
examples/directory for more sample programs - Read
docs/beamtalk-language-features.mdfor full language syntax - Check out
docs/beamtalk-principles.mdto understand the design philosophy - Review
docs/known-limitations.mdfor what's not yet supported - Try building your own actors and experimenting with message passing!
- Type
:helpin the REPL for command reference - See
README.mdfor installation and setup - Report issues at: https://github.com/jamesc/beamtalk/issues
Happy coding! 🚀