Lisp インタプリターの書き方(Python で)(2010)

2026/06/22 0:36

Lisp インタプリターの書き方(Python で)(2010)

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

Scheme のインタープリタを Python で実装するプロジェクト「Lispy」(

lis.py
)は、基礎的なコンピュータサイエンスの原則と、アラン・ケイが「ソフトウェアのマクスウェル方程式」と呼んだ概念を示す教育用的モデルとして機能します。スティーブ・エギエが指摘したように、コンパイラまたはインタープリタを理解することは、コンピュータがどのように動作するかを理解するために不可欠です。このプロジェクトは、変数参照、定数、条件分岐(
if
)、定義(
define
)、手続き呼び出しを処理するため、Scheme のミニマリストな構文(Python では 33 語、Java では 50 語と比較してわずか 5 のキーワードのみ)を利用した単純な「Lispy カルキュレータ」から始まります。この初期段階では、入力を
tokenize
および
read_from_tokens
を通じて木構造に変換し、抽象的構文木(AST)を形成します。この AST が 2 つの段階を持つインタープリタである
parse
eval
に引き渡されます。型は Python オブジェクトを用いてモデル化されており、
Symbol
(str)、
Number
(int/float)、
Atom
List
(list)、および環境を管理する
Env
(dict)が含まれます。標準的な環境には数学関数と演算子が含まれています。レキシカルスコーピングを扱うため、
Env
クラスは
outer
ポインタを追加して拡張され、手続きはパラメータ、本体、関連付けられた環境を持つオブジェクトとして保存されます。このプロジェクトは、
quote
set!
lambda
などの特殊な形式を追加することで、「フル Lispy」に進化します。尾再帰最適化の欠如、コメント、文字列/ブール型のサポート不足、エラー回復機構の不備といった制限が存在するものの、最終的な 117 ラインの核となる実装では
(fact 100)
の計算がわずか 0.003 秒で完了します。テキストは、ピーター・ノルヴィグによる Lisp インタープリタを C で实现したというエピソード(修士論文のため)で締めくくり、ソフトウェアに関する深い理解はインタープリタを使うことよりも、それらを分解することから得られることを強調しています。

本文

Lispy: Python を用いた Scheme 方言インタプリタの実装

この記事では、コンピュータ言語のインタプリタを実現する一般的な方法を解説するとともに、Python 3 を実装言語として Lisp のスキーム(Scheme)方言の大部分を実装可能なインタプリタ

Lispy
(lis.py) を作成します。

  • 目的: アラン・ケイが「ソフトウェアのマクスウェルの方程式」と呼んだ内容(言語定義と解釈の関係)を簡潔かつ明快に示すこと。
  • 重要性: スティーブ・イエグーは、「コンパイラ(あるいはインタプリタ)の動作原理を理解していない人は、コンピュータの真の仕組みを理解したとは言えない」と述べている。

1. スキームプログラムの構文と意味

基本概念

  • 構文: 正しい演算子や式を形成するための文字の配置仕方。
  • 意味: 演算子または式の持つ内容(価値)。
  • 評価: 式に値を決定させる処理。例:
    1 + 2 ⇒ 3

スキームのシンプルな構文

スキームは Java や Python と異なり、非常に単純で統一された構文を持っています。

特徴詳細
式のみステートメントと式の区別はない(すべてのプログラムは式)。
原子的表現数値や記号は部品に分解されない原子。演算子(
+
,
>
)も記号として扱われる。
リスト表現ほとんどすべてはリストで表される(
(
で始まり、
)
で終わる)。
特殊形式 vs 関数呼び出しリストの最初の要素が意味を決定。
- キーワード(
if
)は特殊形式。演算規則はキーワード次第。
- 非キーワード(
fn
,
+
)は関数呼び出し
  • 比較: スキームはわずか 5 つのキーワードと 8 つの構文形式で完結する一方、Python は 33 キーワード・110 形式、Java は 50 キーワード・133 形式を持つ。
    • 注釈: 「Lots of Irritating Silly Parentheses(Lisp)」という冗談に対し、「Lisp Is Syntactically Pure(スキームは構文的に純粋)」と解釈します。

2. 言語 1: Lispy カルキュレータ

まずは簡易版の「Lispy Calculator」から始めます。これは前置記法を用いた電卓のようなものです。

定義可能な表現

以下の 5 つの構文形式のみを使用します。

表現構文意味と例
変数参照
symbol
記号を変数名として扱い、値を取得。例:
r ⇒ 10
定数リテラル
number
自身に評価される。例:
12 ⇒ 12
条件式
(if test conseq alt)
テストが真なら
conseq
、否なら
alt
を返す。
定義
(define symbol exp)
変数
symbol
exp
の評価結果を割り当てる。
手続き呼び出し
(proc arg...)
引数を評価し、関数に適用する(
if
,
define
,
quote
の除外)。

インタプリタの構成要素

インタプリタは主に 2 つのプロセスで成り立ちます。

graph LR
    A[プログラム文字列] -->|パース | B[抽象構文木 (AST)]
    B -->|評価 | C[実行結果]
  1. パース (
    parse
    )
    : 文字列をトークン化し、内部表現(AST)に変換。
  2. 実行 (
    eval
    )
    : AST を意味規則に従って処理して計算を行う。

パースの実装 (
parse
,
tokenize
,
read_from_tokens
)

  • トークン化:
    str.split
    を使い、括弧の周囲にスペースを挿入して分割。
  • 解析: 括ごとの構造を確認し、記号/数値を識別して AST を構築。

コード例:パース関数

def tokenize(chars: str) -> list:
    return chars.replace('(', ' ( ').replace(')', ' ) ').split()

def parse(program: str) -> Exp:
    return read_from_tokens(tokenize(program))

def read_from_tokens(tokens: list) -> Exp:
    if len(tokens) == 0:
        raise SyntaxError('unexpected EOF')
    token = tokens.pop(0)
    if token == '(':
        L = []
        while tokens[0] != ')':
            L.append(read_from_tokens(tokens))
        tokens.pop(0) # ')' を削除
        return L
    elif token == ')':
        raise SyntaxError('unexpected )')
    else:
        return atom(token)

def atom(token: str) -> Atom:
    try: return int(token)
    except ValueError:
        try: return float(token)
        except ValueError:
            return Symbol(token)

評価の実装 (
eval
) & 環境 (Env)

変数の値を取得するためには、**環境(Environment)**が必要です。環境は「変数名 → 値」のマッピング表です。

型定義と標準環境

# スキームオブジェクトの Python での表現
Symbol = str              # 記号
Number = (int, float)     # 数
Atom = (Symbol, Number)   # 原子
List = list               # リスト
Exp = (Atom, List)        # 表現
Env = dict                # 環境(変数: 値の辞書)

# 標準関数と演算子を初期化
def standard_env() -> Env:
    env = Env()
    import math
    import operator as op
    env.update(vars(math)) # sin, cos, sqrt, pi など
    env.update({
        '+': op.add, '-': op.sub, '*': op.mul, '/': op.truediv,
        '>': op.gt, '<': op.lt, 'eq?': op.eq, 'sqrt': math.sqrt,
        # ... 他多数の関数定義 ...
    })
    return env

global_env = standard_env()

評価関数
eval
のロジック

構文処理
変数 (
symbol
)
env[variablename]
で値を取得。
数値 (
number
)
自身をそのまま返す。
ifテストを評価し、真偽に応じて枝を選択して再帰評価。
define環境を更新し、新しい変数を追加。
呼び出し引数をすべて評価し、手続き関数に適用 (
proc(*args)
)。
def eval(x: Exp, env=global_env) -> Exp:
    if isinstance(x, Symbol):        # 変数参照
        return env[x]
    elif isinstance(x, Number):      # 定数
        return x                
    elif x[0] == 'if':               # 条件式
        _, test, conseq, alt = x
        exp = (conseq if eval(test, env) else alt)
        return eval(exp, env)
    elif x[0] == 'define':           # 定義
        _, symbol, exp = x
        env[symbol] = eval(exp, env)
    else:                            # 関数呼び出し
        proc = eval(x[0], env)       # 手続きの評価
        args = [eval(arg, env) for arg in x[1:]]
        return proc(*args)

対話型環境 (REPL)

read-eval-print loop
を実現する
repl
関数。

def repl(prompt='lis.py> '):
    while True:
        val = eval(parse(raw_input(prompt)))
        if val is not None: 
            print(schemestr(val))

def schemestr(exp):
    if isinstance(exp, List):
        return '(' + ' '.join(map(schemestr, exp)) + ')' 
    else:
        return str(exp)

3. 言語 2: フル Lispy

より完全な言語にするため、以下の 3 つの新しい構文形式を追加します。

  • quote
    : 文字通り評価せず、そのまま返す。
  • set!
    : 変数に値を割り当てる(再割り当て)。
  • lambda
    : ローカル変数を持つ手続きを作成する。

手続きとローカル環境の必要性

単純な辞書ではスコープを区別できません。「関数内の局部変数」がグローバル変数を指してしまう問題を解決するため、階層構造を持つ環境クラスを導入します。

新しい環境クラス
Env
Procedure

class Env(dict):
    "環境:辞書サブクラス。内側の環境を持つ。"
    def __init__(self, parms=(), args=(), outer=None):
        self.update(zip(parms, args)) # ローカル変数の割り当て
        self.outer = outer
    def find(self, var):
        "変数を最優先で検索(リーザルスコープ)"
        return self if (var in self) else self.outer.find(var)

class Procedure(object):
    "ユーザー定義手続き"
    def __init__(self, parms, body, env):
        self.parms, self.body, self.env = parms, body, env
    def __call__(self, *args): 
        # 新しい環境を作成し、ローカル変数を持つ状態で評価
        return eval(self.body, Env(self.parms, args, self.env))

# グローバル環境を保持
global_env = standard_env()

拡張された
eval

  • 変数参照:
    env.find(var)
    を使用して、内側のスコープから外側へ検索。
  • quote: 引数をそのまま返す(評価しない)。
  • set!: 存在する環境の変数に値を設定。
  • lambda: 新しい
    Procedure
    オブジェクトを生成。
def eval(x, env=global_env):
    if isinstance(x, Symbol):      # 変数参照 (スコープ検索)
        return env.find(x)[x]
    elif not isinstance(x, List): # 定数
        return x   
    op, *args = x       
    if op == 'quote':              # クォート
        return args[0]
    elif op == 'if':               # 条件式
        (test, conseq, alt) = args
        exp = (conseq if eval(test, env) else alt)
        return eval(exp, env)
    elif op == 'define':           # 定義 (常に最も内側)
        (symbol, exp) = args
        env[symbol] = eval(exp, env)
    elif op == 'set!':             # 割り当て
        (symbol, exp) = args
        env.find(symbol)[symbol] = eval(exp, env)
    elif op == 'lambda':           # 手続き作成
        (parms, body) = args
        return Procedure(parms, body, env)
    else:                          # 関数呼び出し
        proc = eval(op, env)
        vals = [eval(arg, env) for arg in args]
        return proc(*vals)

フル機能のデモ

  • 再帰: ファクツリアルやフィボナッチ計算。
  • 束縛(Binding): 関数内部での変数更新は、外部環境に影響しない。
  • 高階関数:
    map
    ,
    filter
    のような処理が可能。
>>> (define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))
>>> (fact 10)
3628800

4. Lispy の特性評価

サイズと効率

  • 小さい: コメント・空白除くと117行(約 4KB)。ソースコードの軽量化に成功。
  • 速い:
    (fact 100)
    を計算する時間は 0.003 秒。十分高速。

機能と制限

  • 完全性: スキーム標準より若干劣るが、核心機能を備えている。
    • 欠けている構文: コメント、
      cond
      ,
      let
      ,
      quasiquote
      など。
    • 欠けている型: 文字列、ブール値、ポートなど。
    • 欠ける特性: Tail Call Optimization (テイル再帰)、Continuation (
      call/cc
      ) など。
  • エラー回復: エラーを許容せず、妥当なプログラムを書くことを前提とする。

「真実の話」:言語の境界線

インタプリタの実装には面白いエピソードがある(Peter Norvig による)。 1984 年、共同研究者 Tony DeRose は LaTeX 以前のテキスト処理のために「前方参照可能なラベル」という機能を求めたが、当時の Lisp が非-LaTeX 表現の読み出しに遅すぎた。そこで二人は役割を分担した:

  • Tony (C): プログラムを C コードに変換するループを実装(Python と異なり、高速な C を用いる)。
  • Peter (Lisp): その変換されたコードを解釈するための Lisp インタプリタを実装。

これにより、「最も強力な言語の一つ」(Alan Kay 談)であるインタプリタは、CとLispのハイブリッドとして生まれ、二人とも博士号を取得した。

5. まとめと参考資料

この

Lispy
は、Python を用いて Scheme の基本的な概念(構文解析、評価モデル、環境スコープ、手続き束縛)を理解するための最小限かつ完全な実装です。

より深く学ぶには

  • 書籍: 『The Little Schemer』(Friedman, Felleisen), 「Scheme 入門」など。
  • 動画: SICP (Structure and Interpretation of Computer Programs) の講義。
  • ソースコード: より高度な
    Lispy
    のバージョンや解説は別ページで扱っている。

同じ日のほかのニュース

一覧に戻る →

2026/06/22 6:29

アペルトス:主権 AI 向けのオープンファウンデーションモデル

## Japanese Translation: スイス AI イニシアチブによって、EPFL、チューリッヒ工科大学(ETH)、CSCS の協力により、画期的な完全にオープンな多言語 AI モデルが開発されました。本モデルは、全データの透明性を確保しながら強力な技術への即時アクセスを提供します——再現性を確保するため、すべてのデータ、コード、重み付け、手法、アライメント原則が文書化されています。1,000 以上の言語から DAY ONE にトレーニングを開始し、8B および 70B パラメータ規模の両方で競争力を誇る性能を実現しています。EU AI アクトの要件に厳格に準拠しており、データ opting-out を尊重し、個人情報を削除するとともに、メモライゼーションを防止します。スイスエコシステム内でスイスコミが戦略パートナーとして携わっており、本イニシアチブはさらなる開発のためのグローバルなスケーラブルな基盤となっており、強力な能力と責任ある管理の間でバランスを保つ道徳的で再現可能な枠組みを確立しています。

2026/06/22 6:40

私の以前の職場は不正行為があるためにだけ存在していたのでしょうか?

## Japanese Translation: 最も重要な教訓は、UK のスタートアップ GenieDB が、そのコア技術革新は後に真面目な業界プレイヤーによって採用されたにもかかわらず、ポートフォリオの成長のためではなく、投資家の資金を手数料主導で吸い上げるための仕組みを可能にするために、Stuart Frost 所有の US ベンチャーファンド「Frost VP」に買収された点である。GenieDB は M&A を狙って収益機会を積極的に見送っており、顧客数は最大 3 ヶ社を超えたことがなかった。また、チームが交代させられ、U.S. に残ったのは著者の一人だけだった。10 年後、Frost VP が SEC から詐欺容疑で訴えられたというニュースが明らかになり、同社は関連のないサービス(例:料理人を雇う個人シェフサービス、クリーニング業者、ビザスポンサーシップやマーケティング会社など)に対して過剰な手数料を徴収するインキュベーターとして活動していたことが判明した。仲裁の結果、投資家が勝訴し、反訴では上記のような詐欺の嫌疑が詳細に記載されていた。Frost は当初、投資家との共謀を主張したが、証拠に立ち向かうことができず敗訴した。GenieDB の CEO やインサイダーは両方とも、投資がこの手数料モデルに動機付けられていたことを認めたが、裁判所は GenieDB がなぜポートフォリオに加わったのかについて具体的な理由を判定することはなかった。このスキームは資金残高を枯渇させ、著者の人生軌道(U.S. 市民権の喪失やキャリアの安定性への影響を含む)を変え、さらに Frost から資金管理の禁止という恒久的な禁制を下すことで、事実上 Frost VP の運営を終了させた。この事例は、高額手数料型のインキュベーターモデルについて鋭い警告を示しており、詐欺的な手数料構造が正当な企業価値を覆い隠し、投資家や従業員双方に壊滅的な長期的影響をもたらす可能性があることを示している。

2026/06/22 6:10

すべては対数なりである

## Japanese Translation: 以下の文章は、情報のすべてを維持しつつ、流れと簡潔性を向上させるための改訂版である: > 本書は、対数($\log N$)を具体的な底を持たない抽象的な実体として捉え直すことで、特定の単位や底が選択される場合にのみ数値的値を獲得するという視点を提案する。この見解は、点そのものが基礎的なものであり、原点の選択を行うことで初めて数値的な変位(displacements)となるベクトル幾何学の概念と直接的な対応を確立する。この枠組みにおいて、対数の底の変更は物理的な単位系の変更、あるいは微積分における微分形式の書き換えに等しい。この議論は対称性を他の分野にも拡張しており、$p$ 進評価($p$-adic valuation)や複素解析における消滅位数(order of vanishing)は、対数代数的構造内での投影として機能し、さらにベクトル空間の対数としての振る舞いを見せる次元などは、分数次元といった仮想的な構成すら許容する。自然対数を多項式の振る舞いと関連付ける極限や導関数を通過して再定義することで、本書は次元、導関数、評価という一見異なる概念が、乗法的構造と加法的结构との間のより深い同型写像(isomorphism)の現れであるとして論じる。結局のところ、この視点を採用することにより、数学に一般共変性(general covariance)を適用することは、冗長な単位依存記号によって遮られている単純で座標freeな実在にアクセスする手段を示唆している。

Lisp インタプリターの書き方(Python で)(2010) | そっか~ニュース