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 パターンクラスで、ディスパッチャがリクエスト / レスポンスの形を整える。


さらに

同梱されているサンプルがカバーする領域は次のとおり。