Lambda Prelude の主要機能を見せるサンプル集。各ブロックはそれ単体で動くスクリプト。.lp ファイルとして保存し、インタプリタで実行するかネイティブバイナリにビルドする。
lambda-prelude run hello.lp
lambda-prelude build http-server.lp --out http-server
スモークテスト
"スモークテスト: 最小スクリプト"
5 factorial printNl.
プロトコルが継承の代替
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 — Comparable から導出"
(a max: b) value printNl. "5"
クラスは < だけを実装。残りはプロトコルから自動的に導出される。
OCaml 5 Effects 上の ping-pong アクター
"アクター表層は spawn / send / receive の 3 つの通信プリミティブと、自分自身を得る Actor self から始まる:
Actor self — 現在の fiber のアクター参照
Actor spawn: [:me | ..] — 新規アクターを生成
actor ! msg — fire-and-forget 送信
actor receive — 次のメッセージが来るまでブロック"
parent := Actor self.
child := Actor spawn: [:me |
msg := me receive.
msg printNl.
parent ! #pong
].
child ! #ping.
parent receive printNl.
型付きディスパッチを持つステートフルアクター
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 は可変状態を持たない — n は再帰呼び出し Counter run: me count: n のループ引数として生きる。型付きの receive: 表が各メッセージクラスを型推論に通すので、ハンドラ本体は具体型として msg を扱える。
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.
標準の Dict は永続 HAMT。at:put: は新しい Dict を返し、元の Dict は変更されない。
HTTP サーバ
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/
HTTP サーバはリクエストハンドラとしてブロックを取る。各リクエストはアクタースケジューラ上で新規 fiber として走る — 遅い上流が他のハンドラを止めることはない。
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 は組み込み。PostgreSQL と MariaDB / MySQL は、インタプリタでも AOT バイナリでも Dynlink プラグインとして読み込まれる。
JSON-RPC ディスパッチ
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: は単一リクエスト、バッチ、通知、未知メソッド、ハンドラ例外を受ける — 未知や raise は -32603 エラーエンベロープに変換される。HTTP ワイヤ越しのラウンドトリップは別サンプルとして同梱されている。
TERIOS 方式のディスパッチ
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.
業務 RPC は serviceName / requestDto / responseDto / errorInfoList の 4 つを組にしたエンベロープで運ぶ。サービスは小さな Command パターンクラスで、ディスパッチャがリクエスト / レスポンスの形を整える。
さらに
同梱されているサンプルがカバーする領域は次のとおり。
- 言語基礎 — 算術、ブロック、再帰、論理、カスケード
- クラスとプロトコル —
fields:、クラスメソッド、複数プロトコル合成 - アクターとスーパーバイザ — 選択受信、受信タイムアウト、Future、リンク、モニタ、終了シグナル捕捉、スーパーバイザ戦略
- I/O とネットワーク — ファイル I/O、JSON、標準入力、TCP、TLS
- サービス — HTTP サーバ / クライアント、SQLite、PostgreSQL、MariaDB / MySQL、Redis / Valkey、JSON-RPC、TERIOS 方式のディスパッチ
- 分散 — マルチドメイン spawn、リモート参照 (
Remote at:for:id:)、仮想アクター (grain: 単一ノード / クロスノード / インメモリストア) - 運用 — リフレクション、サブプロセス、キャッシュ、コネクションプール