プログラミング
Show HN: E-- – 英語とPythonの間で調整する言語
Show HN: E-- – A language you dial between English and Python (github.com)
要約
E-- (English--) は、曖昧さを排除した英語で記述し、決定論的にPythonにコンパイルされる新しいプログラミング言語です。LLM生成コードの実行時における曖昧さやデバッグの困難さを解消するため、LLMの役割を実行時ではなく、コード作成時に分離します。これにより、LLMの創造性とPythonの確実な実行性を両立させます。
全文翻訳
E-- (English--) は、通常の英語で記述でき、決定論的にPythonにコンパイルされるプログラミング言語です。E--("English--")は、曖昧さを排除した英語であり、閉じられた文法と固定された語彙を持ち、各構成要素に対して正確に1つの正規の表現形式があります。これは、英語のように読み書きでき、かつ通常の再現可能なPythonコードにコンパイルされることを目的としています。
なぜこれが必要か
LLMによって生成されたコードは、実行時に曖昧で、呼び出しごとにコストがかかり、デバッグが困難な場合があります。E--は、LLMの役割を実行から分離します。LLMは、作成時に(オプションで)正規のE--コードを記述し、決定論的なパーサーがE--をPythonにコンパイルします。実行時は純粋なPythonです。これにより、LLMの創造性と、その後の確実な動作という両方の利点が得られます。
クイックスタート
PyPIからインストールします:
pip install e-minus-minus
正規のE--ファイルをPythonにトランスパイルします:
emm-transpile examples/describe.emm
これで完了です。{{ }} スロットのない正規のE--にはLLMは不要です。詳細については、「E--の実行」を参照してください。フリーイングリッシュ入力や値スロットを使用する場合のLLMセットアップについては、「{{ }} スロットの解決」を参照してください。LLMパスはオプションであり、[llm] 追加機能の背後にあります:
pip install "e-minus-minus[llm]"
E--自体の開発
リポジトリをクローンし、作業ツリーがPYTHONPATH上にある間にCLIをモジュールとして呼び出します:
PYTHONPATH=src python -m e_minus_minus.transpiler examples/describe.emm
E--を独自のソフトウェアで使用する
E--はApache License 2.0(LICENSEを参照)の下でライセンスされており、明示的な特許許諾が付与されているため、商用製品に自由に組み込むことができます。2つの明確化:
ライセンスはE--ツールを対象としています。E--が生成するPythonはあなたのものです。出力は、このプロジェクトのライセンスによって制約されません。
LLMはあなた自身が用意します。E--の正規化ツールと{{ }}解決には、あなたが提供する言語モデルが必要であり、そのプロバイダーの条件はこのプロジェクトとは別です。
プログラムAPI:
from e_minus_minus import transpile
python_source = transpile(emm_source)
transpile() は純粋であり、ネットワークや副作用はありません。{{ ... }} スロットを処理するために resolve_slot コールバックを渡します(ドキュメント/spec.md および src/transpiler.py のCLI実装を参照してください)。
仕組み
E--は2段階のパイプラインであり、信頼性の低い部分と決定論的な部分が決して混ざり合わないように分割されています:
フリーイングリッシュ --LLM(トランスパイル時)--> 正規E-- --通常のパーサー--> Python
正規化ツール(LLM、オプション)。自由形式の英語を正規E--に変換します。これは言語的な曖昧さを扱う唯一のステージです。
コンパイラ(決定論的)。正規E--を通常のパーサーでPythonに変換します。LLMは使用せず、完全に再現可能でデバッグ可能です。
LLMはトランスパイル時にのみ実行され、実行時には実行されません。生成されるPythonは常に純粋で自己完結型です。
LLMはプログラム構造を決定することを許可されておらず、明確に区切られた値スロット {{ ... }} に値を埋めるためにのみ使用され、それらの解決はキャッシュされるため、ビルドは再現可能になります。
一例
正規E--:
result を [[fibonacci]]( {{5より大きい最初の素数}} ) に設定します。
[[print]](result) を実行します。
これは以下にコンパイルされます:
result = fibonacci(7)
print(result)
マーカーが曖昧さをなくします:[[name]]は関数呼び出し、単語は変数、"x"/3はリテラル、<1, 2, 3>はリスト、{{ ... }}はトランスパイラが一度解決して埋め込む英語のフレーズです。
E--の実行
E--ソースファイルは .emm 拡張子(English--)を使用します。決定論的な正規からPythonへのコアが実装されており、コマンドラインから .emm ファイルをトランスパイルして実行できます。
以下の正規E--ソース(examples/describe.emm)の場合:
Define [[describe]] taking n:
If n is greater than 10:
Give back "big".
Give back "small".
For each n in <3, 42, 7>:
Do [[print]]([[describe]](n)).
これをトランスパイルして、生成されたPythonを画面に表示します:
python3 src/transpiler.py examples/describe.emm
以下が出力されます:
def describe(n):
if n > 10:
return "big"
return "small"
for n in [3, 42, 7]:
print(describe(n))
生成されたPythonを画面ではなくファイルに書き出すには:
python3 src/transpiler.py examples/describe.emm -o out.py
トランスパイルして実行し、プログラムの実際の出力を確認するには:
python3 src/transpiler.py examples/describe.emm --run
以下が出力されます:
small
big
small
生成されたPythonを表示し、一度に実行するには --show(エイリアス -s)を使用します:
python3 src/transpiler.py examples/describe.emm --run --show
コメント行で区切られたコードとその出力を表示します:
# --- generated Python ---
def describe(n):
if n > 10:
return "big"
return "small"
for n in [3, 42, 7]:
print(describe(n))
# --- output ---
small
big
small
区切り文字はPythonコメントなので、ブロック全体をコピー&ペーストできます。
--show 単独(--runなし)では、デフォルトのようにPythonのみを表示します。
注:
.emm 拡張子はE--ソースファイルの慣例です。
{{ ... }} LLM値スロットは実行可能です。「{{ }} スロットの解決」を参照してください。
スロットのないファイル(examples/describe.emmなど)はキーを必要とせず、--runはモデルなしで動作します。
{{ }} スロットの解決(LLMセットアップ)
{{ ... }} スロットは、トランスパイル時にLLMを使用して一度だけPython式に解決され、その後結果がキャッシュされるため、後続のビルドはオフラインで再現可能になります。スロットのないファイルはAPIキーやセットアップを必要としません。
スロットの例をエンドツーエンドで実行するには:
# 1. 仮想環境を作成してアクティベートします
python3 -m venv .venv && source .venv/bin/activate
# 2. 依存関係をインストールします(Anthropic SDK)
pip install -r requirements.txt
# 3. Anthropic APIキーを設定します
export ANTHROPIC_API_KEY="sk-ant-..."
# 4. スロットの例をトランスパイルして実行します
python3 src/transpiler.py examples/primes.emm --show --run
examples/primes.emm は最小限です:
For each p in {{the first five prime numbers, as a Python list}}:
Do [[print]](p).
これは以下にトランスパイルされます:
for p in [2, 3, 5, 7, 11]:
print(p)
そして、2 3 5 7 11(各行に1つ)を出力します。
{{ ... }} スロットは、LLMを介して一度だけ具体的なリスト [2, 3, 5, 7, 11] に解決され、キャッシュされ、以降のすべてのビルドでAPI呼び出しなしで使用されます。
最初の実行では、モデル(Anthropic Haiku)が各スロットを解決するために呼び出され、解決されたPython式が.emm_cache.jsonに書き込まれ、出力に埋め込まれます。それ以降の実行はオフラインのキャッシュヒットであり、モデル呼び出しはなく、結果は同一です。
キャッシュファイルは、正確なスロットテキストとその解決された式をマッピングし、コミットされることを意図しているため、解決された値は差分確認やレビューが可能です。スロットテキストの編集はキャッシュミスとなり再解決されます。キャッシュを削除すると、完全な再解決が強制されます。
スロットのないファイル(examples/describe.emmなど)はAPIに一切触れません。
ワンライナー値スロットの例
ファイルセットアップなしで、インラインでスロットの例を構築することもできます:
printf 'Set year to {{the current year, as an integer literal}}.\nDo [[print]](year).\n' > hello.emm
emm-transpile hello.emm --show --run
1行目のスロットは式位置(Set year to ... の中)にあるため、LLMは単一のPython式(例:2026)を返し、コンパイラがそれを埋め込みます:
year = 2026
print(year)
2026 を出力します。2回目の実行はオフライン(キャッシュヒット)です。
コードスロット(v0.2.0)
{{ ... }} スロットは、式位置だけでなく、ステートメント位置にも出現できます。単独の行に、ブロックのインデントレベルに配置すると、1つ以上のPythonステートメントをLLMに委任します。
Author writes the surrounding structure; the LLM fills the region.
Define [[summarize]] taking numbers:
{{ compute mean, median and count of numbers into mean_v median_v count_v }}
Do [[print]](count_v).
Do [[print]](mean_v).
Give back mean_v.
トランスパイル時に、ステートメントスロットは実際のPythonに解決されます:
def summarize(numbers):
from statistics import mean, median
mean_v = mean(numbers)
median_v = median(numbers)
count_v = len(numbers)
print(count_v)
print(mean_v)
return mean_v
トレードオフ:[[wikilinks]] またはコードスロットで解決されたPython内の呼び出し可能な参照は、E--ソースを検査する後続のツールからは不透明です。著者は、領域レベルの委任と引き換えに、コードスロット領域内のDAGの可視性を認識して受け入れます。グラフの可視性が必要な場合は式スロットを使用し、LLMが構造を理解している領域を委任する場合はコードスロットを使用します。