[Lisp処理系を作る] 03日目 NIL を作る

本当は List と一緒に作りたかったのだが、かなり複雑だったので独立してみる。

nil の特徴

  • List である
    • car cdr 共に nil を返す
  • Atom である
  • Symbol である
    • それには nil が 束縛 されている
    • "nil" という名前を持っている。

xyzzy では以下は全部 t を返却する。

(listp nil)
(null (car nil))
(null (cdr nil))
(atom nil)
(symbolp nil)
(boundp nil)
(null (symbol-value nil))

こうなってくると、 nil は各オブジェクトの特殊な場合を指すような気がしてくる。 うーむ、多重継承が欲しくなってくる。

どう実装するか?

  • ISymbol インターフェイスを用意する。
    • string Name { get; }
    • SymbolicExpression Value { get; }
    • void Bind( SymbolicExpression s );
    • void Unbind();
  • List インスタンスの car cdr 共に nil の場合を nil とする。
  • List で ISymbol を実装する。 nil でない場合は例外を投げ、 nil 場合はそれに沿った処理をする。
  • Atom を継承し ISymbol を実装して Symbol クラスを作成する。
  • SymbolTable は ISymbol を操作するクラスにする。
    • Nil に関する動作は SymbolTable で頑張ることにする。

これにより

  • 空リストを nil とすることが出来る。
  • List インスタンスの nil に対して List 操作と Symbol 操作が行える
  • Symbol インスタンスの nil に対しては List 操作を行えないので工夫する必要がある。
    • SymbolTable で頑張れば回避可能かも。

NIL 定数を作る

List の特殊なインスタンスを NIL とするので List クラスの static readonly な変数を用意する。

public class List : SymbolicExpression, ISymbol {
  public static readonly SymbolicExpression NIL;
  static List() {
    List nil = new List();
    typeof( List ).InvokeMember(
      "car",
      BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Instance,
      null, nil, new object[]{ nil } );
    typeof( List ).InvokeMember(
      "cdr",
      BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Instance,
      null, nil, new object[]{ nil } );
    List.NIL = nil;
  }
  :
}
  • 通常 List クラスはコンストラクタで引数なしだと car と cdr に定数 NIL を入れておく。
  • この記述中の static コンストラクタ内では 定数 NIL には null が入っている。
    • この時に List インスタンスを作ると car cdr には null が入ってることになる。
  • とりあえず List インスタンスを作る。
  • そこに無理矢理リフレクションを使って car と cdr に自身を代入している。
    • List クラスの car と cdr は readonly なので通常の代入は出来ないから。