はじめに
Lazy K はご存知でしょうか。
S, K, I を組み合わせて記述する純粋関数型言語です。私は「このプログラミング言語がすごい!」を編集した際に知りました。
生成AIでコーディングできるようになった2026年現在、はたして Claude Code で Lazy K のコーディングはできるのでしょうか?
本記事では、実際に検証してみた内容を記します。
Lazy K とは
Lazy K とは、S, K, I の3つのコンビネータのみで計算を記述する純粋関数型言語です。
各コンビネータの動作は次のとおりです。
| コンビネータ | 規則 |
|---|---|
| I | I x = x |
| K | K x y = x |
| S | S f g x = f x (g x) |
変数や糖衣構文は一切なく、すべての計算をこの3規則で表現します。
チューリング完全であり、理論上はあらゆるプログラムが記述できます。
表記法には4種類あります。
-
コンビネータ算法様式(括弧記法):
(S K K)のように括弧で左結合の適用を表す -
Unlambda 様式: バッククォートで前置適用を表す(例:
`sk) -
Iota 様式: ι コンビネータ1種類に還元した表記(例:
*ii) -
Jot 様式:
0と1の2進列で表す
詳細は下記の記事をご参照ください。
Lazy K 開発環境構築
Claude Code に docker-compose.yml で開発環境を構築してもらいました。
生成された構成は、Ubuntu 22.04 イメージ上で msullivan/LazyK のソースコードをビルドするものです。lazy.cpp をコンパイルして /usr/local/bin/lazy に配置し、ワークスペースをボリュームマウントしています。
実行は次のコマンドで行います。< /dev/null は、Lazy K プログラムが入力ストリームをリストのターミネータとして使うため、stdin を即座に EOF にするために必要とのことです。
docker compose run --rm -T lazy-k lazy hello_qiita.lazy < /dev/null
Hello, Qiita! を出力するプログラム
Unlambda 様式での生成
Hello, World を出力するプログラムは記事としてあるため、文字列を変更し、Hello, Qiita! を出力してもらうようにしました。
Sonnet 4.6 に依頼したところ、Lazy K コードを出力する Python コードが出力されました。出力された Lazy K コードは Unlambda 様式であり、確かに期待通りの出力を確認できました。
しかし、Claude Code 自体が Lazy K をコーディングしたとは言い難い。プロンプトを変更し、直接記載するように指示を出しました。
すると、CLAUDE_CODE_MAX_OUTPUT_TOKENS である 32,000 トークン制限に引っかかりました。各文字を Church 数値でエンコードするため、Hello, Qiita!\n の14文字を展開すると約 15,000 文字に達し、会話テキストとして出力しきれないためとのことでした。
コンビネータ算法様式での実装
改めて、コンビネータ算法様式(括弧記法)で Hello, Qiita! を出力するプログラムを生成してもらいました。出力トークン制限があり、こちらも Python でワンライナーで計算してファイルに直接書き込んでもらう形となった。
以下では、このスクリプトがどのような原理で SKI 式を組み立てているかを見ていきます。
Lazy K の出力の仕組み
Lazy K では出力を「文字コードのリスト」として表現します。[72, 101, 108, ...](= H, e, l, ...)というリストを返す関数がプログラムとなります。
Church 数値
Church 数値とは、自然数を「関数の繰り返し回数」として表現する方法です。数値そのものではなく、「f を n 回繰り返す」という操作を関数として表します。
c(0) = x (f を0回繰り返す)
c(1) = f(x) (f を1回繰り返す)
c(2) = f(f(x)) (f を2回繰り返す)
c(3) = f(f(f(x))) (f を3回繰り返す)
SKI コンビネータでは c(0) = (KI)、後継関数 succ = (S(S(KS)K)) を n 回適用すると c(n) になります。cons_head と組み合わせることで、繰り返し回数が文字コードとして解釈されるようになります。
cons_head(c) — 文字をリストの先頭に追加する関数
cons_head('H') は「受け取ったリストの先頭に H(文字コード 72)を付け加えて返す」関数と表現します。
cons_head('H')(rest) = [72, ...rest]
これを SKI コンビネータで表すと S(K(S(SI(K c(72))))K) になります。c(72) は「72」という数値を表す Church 数値です。
compose(f, g) — 関数を直列につなぐ
compose(f, g) は「まず g を適用し、その結果に f を適用する」関数合成として表現します。
compose(cons_head('H'), cons_head('e'))(rest)
= [72, 101, ...rest]
SKI では S(Kf)(g) と表します。
プログラム全体の構造
この2つを組み合わせると、文字列全体を出力する関数が作れます。
compose(cons_head('H'),
compose(cons_head('e'),
compose(cons_head('l'),
...
compose(cons_head('\n'), I)...)))
I(恒等関数)が末尾で、stdin の EOF をそのまま返してリストを終端させます。入力に空の stdin を渡すと、H → e → l → l → o → ... → \n と順番に出力されます。
cons_head('t') の中の Church 数値 c(116) は「((S(S(KS)K)) を116回ネスト」した式で表現されるため、それだけで1,628文字になります。14文字分すべて展開すると合計約15,000文字に達してしまいます。
生成されたファイルは約 15,000 バイトで、実行すると正しく Hello, Qiita! が出力されました。
コードはこちらになります。
おわりに
「Claude Code で Lazy K のコードは書けるのか?」の問いに対する回答は「原理的には書けるが、直接出力することは難しかった」でした。
SKI コンビネータの導出(Church 数値・cons セル・関数合成)を理解しており、Unlambda 様式・コンビネータ算法様式の両方で動作するプログラムを生成できました。実行結果として Hello, Qiita! が出力されることも確認しています。
しかし、Lazy K のコードを会話テキストとして直接出力することはできませんでした。式が指数的に膨らむ言語の特性上、14文字分の出力で約 15,000 文字に達してしまい、CLAUDE_CODE_MAX_OUTPUT_TOKENS の 32,000 トークン制限に引っかかりました。最終的には Python スクリプトにファイルへ直接書き込ませる迂回が必要となりました。