Build from Source
Lambda Prelude is built from OCaml source with dune.
# Requires: OCaml 5.2+ (verified on 5.5), dune 3.0+, menhir 2.1+
git clone <repo-url> lambda_prelude
cd lambda_prelude
dune build
On Windows, the binary is a true native MinGW-w64 executable — no Cygwin DLL at runtime. Source disclosure is under NDA; please get in touch before evaluating for production.
Hello, Prelude
Create hello.lp:
'Hello, Lambda Prelude!' printNl.
Run it under the interpreter:
lambda-prelude run hello.lp
Build it as a native binary:
lambda-prelude build hello.lp --out hello
./hello
The interpreter is for iteration. The AOT binary is the semantic record — when interpreter and AOT disagree, the AOT binary wins.
A Counter Class
Lambda Prelude is functional: values are immutable and classes do not declare instance variables. Each object is an immutable record over fields:.
Object subclass: #Counter fields: (value).
Counter class >> of: n = Counter fields: { value: n }.
Counter >> value = value.
Counter >> inc = Counter fields: { value: value + 1 }.
c := Counter of: 41.
c inc value printNl. "=> 42"
inc returns a new Counter. There is no := reassignment in user code — the only binding form is let, written as := at top-level statements.
Branching is Polymorphic Dispatch
There is no if/match at the surface. Branching is done through messages:
(score > 90)
ifTrue: ['A' printNl]
ifFalse: ['B' printNl].
name ifPresent: [:n | n printNl]
ifAbsent: ['anonymous' printNl].
xs ifEmpty: ['empty list' printNl]
ifNotEmpty: [:h :t | h printNl].
This is the same convention used by traditional Smalltalk, applied uniformly: every conditional, every option / list destructuring, every retry / timeout branch is a message send.
Protocols Replace Inheritance
Classes do not inherit. Shared behaviour is expressed as Protocols, which act like type classes. A class declares the protocols it satisfies in its protocols: clause.
Protocol subclass: #Comparable
requires: (#<).
Comparable >> > other = other < self.
Comparable >> min: o = (self < o) ifTrue: [self] ifFalse: [o].
Comparable >> max: o = (self < o) ifTrue: [o] ifFalse: [self].
Object subclass: #Score
fields: (points)
protocols: (Comparable).
Score class >> of: n = Score fields: { points: n }.
Score >> points = points.
Score >> < other = points < other points.
((Score of: 10) max: (Score of: 3)) printNl.
Score implements <; >, min:, and max: come from Comparable for free.
Two Execution Modes
The same source runs two ways. They share the standard library, the type system, and the actor model.
| Capability | interpreter | AOT |
|---|---|---|
| Tree-walk for development iteration | yes | — |
| Direct OCaml calls for statically resolvable sends | — | yes |
| Runtime dispatcher for dynamic sends | yes | yes |
| Multi-domain work-stealing scheduler | yes | yes |
Plugins via Dynlink (PostgreSQL, MariaDB, MySQL, Redis) | yes | yes |
Use run for iteration. Use build for production — the AOT binary lowers statically resolvable sends to direct OCaml calls and routes the rest through runtime method dispatch.
CLI Reference
lambda-prelude run script.lp # tree-walk interpreter
lambda-prelude build script.lp --out binary # AOT to native
lambda-prelude test tests # run Test API specs
lambda-prelude new my-service # scaffold a project
Debug tooling:
lambda-prelude run script.lp --trace-mailbox # log every mailbox enqueue / receive
lambda-prelude run script.lp --quiet # suppress unmatched-message + actor-death reports
lambda-prelude run script.lp --scheduler multi 4 # N-worker domain pool + auto-distribute
lambda-prelude build script.lp --emit # print the lowered OCaml to stdout
lambda-prelude build script.lp --out script.ml # write the lowered OCaml to a fileModule Resolution
A name in module Main imports: (Http, MyLib). is looked for in the importing file's own directory, then in the directories listed in LAMBDA_PRELUDE_PATH, then in stdlib. The separator is the platform's PATH separator (; on Windows, : on POSIX). A single-segment name with no .lp of its own (Torm, say) resolves to a plugin.
LAMBDA_PRELUDE_PATH=src lambda-prelude run tests/my_test.lp # a test in tests/ importing modules from src/
run, build, the language server and the debug adapter share one loader, so a name that resolves for run resolves for build and in the editor. A resolution failure lists the directories that were actually tried.
Editor
The VS Code extension (lambda-prelude) contributes syntax highlighting, a language server (parse and type-error diagnostics, hover showing inferred types, completion, go-to-definition, find references, rename), and a debug adapter (breakpoints, stepping, and variable inspection, running on the interpreter). Hover answers for the innermost expression the cursor is inside.
The extension bundles the language server, the debug adapter, stdlib and the plugins, so a workspace with no build artefacts of its own still gets imports: and dao: resolved in its diagnostics. Settings: lambdaPrelude.importPath (the LAMBDA_PRELUDE_PATH above; relative entries are taken from the workspace root, and it reaches the language server, the debug adapter and the Run command alike), and serverPath / cliPath / debugAdapterPath to point at binaries other than the bundled ones.
Next Steps
- Examples — 128 runnable Lambda Prelude files
- Actors — mailbox, selective receive, supervision
- AOT — how Lambda Prelude source becomes a native binary
- API Reference — built-in classes and standard library