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.

CapabilityinterpreterAOT
Tree-walk for development iterationyes—
Direct OCaml calls for statically resolvable sends—yes
Runtime dispatcher for dynamic sendsyesyes
Multi-domain work-stealing scheduleryesyes
Plugins via Dynlink (PostgreSQL, MariaDB, MySQL, Redis)yesyes

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 file

Module 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