Selected snippets that demonstrate Lambda Prelude in practice. Each block is a self-contained, executable script. Save as a .lp file and run with the interpreter, or build to a native binary.

lambda-prelude run   hello.lp
lambda-prelude build http-server.lp --out http-server

Smoke test

"Smoke test: simplest possible script."
5 factorial printNl.

Protocols replace inheritance

Protocol subclass: #Comparable
  requires: (#<).

Comparable >> > other    = other < self.
Comparable >> <= other   = (self > other) not.
Comparable >> >= other   = (self < other) not.
Comparable >> min: other = (self < other) ifTrue: [self] ifFalse: [other].
Comparable >> max: other = (self > other) ifTrue: [self] ifFalse: [other].

Object subclass: #Counter
  fields:    (value)
  protocols: (Comparable).

Counter class >> of: v = Counter fields: { value: v }.
Counter >> value       = value.
Counter >> < other     = value < other value.

a := Counter of: 3.
b := Counter of: 5.

(a < b) printNl.            "true"
(b > a) printNl.            "true   — derived from Comparable"
(a max: b) value printNl.   "5"

The class implements only <. Everything else is derived from the protocol.


Ping-pong actors on OCaml 5 effects

"The actor surface starts with three communication primitives — spawn, send, receive — plus Actor self for the current fiber:
   Actor self              — current fiber's actor ref
   Actor spawn: [:me | ..] — spawn a new actor
   actor ! msg             — fire-and-forget message send
   actor receive           — block until next message arrives"

parent := Actor self.

child := Actor spawn: [:me |
  msg := me receive.
  msg printNl.
  parent ! #pong
].

child ! #ping.
parent receive printNl.

Stateful actor with typed dispatch

Object subclass: #Inc.
Object subclass: #GetCount fields: (reply).
Object subclass: #Counter.

GetCount >> reply = reply.

Counter class >> run: me count: n =
  me receive: {
    Inc      -> [:msg | Counter run: me count: n + 1].
    GetCount -> [:msg | msg reply ! n. Counter run: me count: n]
  }.

parent  := Actor self.
counter := Actor spawn: [:me | Counter run: me count: 0].

counter ! (Inc fields: {}).
counter ! (Inc fields: {}).
counter ! (Inc fields: {}).
counter ! (GetCount fields: { reply: parent }).

parent receive printNl.   "3"

Counter holds no mutable state — n lives in the recursive Counter run: me count: n loop argument. The typed receive: table carries each message class through type inference so handler bodies see concrete types.


JSON

obj := JSON parse: '{"name": "Alice", "age": 30, "active": true}'.
(obj at: 'name') printNl.
(obj at: 'age')  printNl.

nested := JSON parse: '{"user": {"id": 42, "tags": ["admin", "dev"]}}'.
((nested at: 'user') at: 'id') printNl.
((nested at: 'user') at: 'tags') size printNl.

d := Dict new.
d := d at: 'name' put: 'Bob'.
d := d at: 'age'  put: 25.
(JSON serialize: d) printNl.

The standard Dict is a persistent HAMT. at:put: returns a new Dict; the original is unchanged.


HTTP server

module Main imports: (Http).

HttpServer
  start: 8080
  handler: [:req |
    HttpResponse fields: {
      status: 200,
      headers: (Dict new at: 'Content-Type' put: 'text/plain'),
      body: 'Hello from Lambda Prelude!\n'
    }
  ].
lambda-prelude build http-hello.lp --out hello-server
./hello-server
curl -i http://localhost:8080/

The HTTP server takes a block as the request handler. Each request runs on a fresh fiber under the actor scheduler; a slow upstream cannot pin other handlers.


SQLite

db := DB open: ':memory:'.

db exec: 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)'.

db exec: 'INSERT INTO users (name, age) VALUES (?, ?)'
  params: (Array with: 'Alice' with: '30').
db exec: 'INSERT INTO users (name, age) VALUES (?, ?)'
  params: (Array with: 'Bob' with: '25').

rows := db query: 'SELECT name, age FROM users ORDER BY age'.
((rows at: 1) at: 'name') printNl.    "Bob"
((rows at: 1) at: 'age')  printNl.    "25"

db close.

SQLite is built in. PostgreSQL and MariaDB / MySQL are loaded as Dynlink plugins, under both the interpreter and the AOT binary.


JSON-RPC dispatch

module Main imports: (Http, JsonRpc).

Object subclass: #Demo.
Demo >> add:      params = (params at: 1) + (params at: 2).
Demo >> subtract: params = (params at: 1) - (params at: 2).
Demo >> fail:     params = Error raise: 'something broke'.

server := JsonRpcServer onService: (Demo fields: {}).

req := Dict from: {
  jsonrpc: '2.0',
  method:  'add',
  params:  (Array with: 7 with: 5),
  id:      1
}.

(JSON serialize: (server dispatchOne: req)) printNl.

server dispatchOne: accepts single requests, batches, notifications, unknown methods, and handler exceptions; unknown methods and raised exceptions become -32603 envelopes. The HTTP wire round-trip is shipped as a separate example.


TERIOS-style envelope dispatch

module Main imports: (Http, Terios::Rpc).

Object subclass: #XA001Save.

XA001Save >> execute: requestDto =
  (requestDto includesKey: 'name')
    ifTrue:  [Res ok:    (Dict from: { id: 42, status: 'created' })]
    ifFalse: [Res error: 'name is required'].

registry := Dict new.
registry := registry at: 'XA001Save' put: (XA001Save fields: {}).

dispatcher := ServiceDispatcher withRegistry: registry.

Business RPC carries serviceName, requestDto, responseDto, and errorInfoList envelopes. Services are small Command-pattern classes; the dispatcher normalises request / response shapes.


More

The full set of bundled examples covers: