Common Lispのシンボル表記は
なぜ統一されていないのか

  • Common Lispのシンボル表記は、いろんなパターンがあります。
  • loop の節語は裸のシンボル、関数のキーワード引数はコロン付き、ラムダリストは&付きと、フォームごとに異なる表記規則を持っています。
  • defclass や defpackage はマクロ構文の外側が裸シンボルでありながらオプション部はキーワードという混在形で、設計時期の違いがそのまま残っています。

関連記事

1. 不統一なシンボルの書き方

Common Lispを読んでいると、同じS式の中でもシンボルの書き方がフォームによってバラバラなことに気づきます。

(loop for x in xs
      when (oddp x)
      collect x)
(declare (type fixnum n))

(make-array 10 :element-type 'fixnum)
(sort xs #'< :key #'car)

(defun make-user (name &key (role :guest) (active t))
  (list :name name :role role :active active))

(make-user "Bob" :role :admin :active nil)

(defpackage my.pkg
  (:use :cl)
  (:export :foo :bar))Code language: Lisp (lisp)

loopの中ではforcollectと書くのに、make-arrayでは:element-typeと書く。
defunの引数表には&keyが出てきます。
どれも「構文上のキーワード」に見えますが、表記の規則が違います。

この不統一は、Common Lispが複数の系譜を持つ方言を統合して生まれたという事実から来ています。
1981年、DARPAの後援でMIT、CMU、Symbolics、Xeroxなどの研究グループが集まり、乱立していたLisp方言を一本化しようとしました1
ZetaLisp(Lisp Machine Lisp)、MacLisp、NIL、Spice Lisp、Schemeなどがそれぞれ独自の構文を持っていたため、Common Lispはこれらを「共通仕様」として定義しましたが、各機能の設計は元の方言から引き継いでいます2
フォームごとに設計哲学が異なる部分が残ったのは、このためと言えます。

1.1. 表記が混在する理由の整理

フォーム対象の語表記出自
loopfor, collect, finally など裸シンボルLisp Machine(英語DSL設計)
declarespecial, type, optimize など裸シンボルMacLisp系の宣言構文
defun/lambda&optional, &rest, &key など&付きシンボル複数方言のラムダリスト統合
make-array:initial-element, :element-type などキーワード関数の&key引数機構
defclass:initarg, :accessor などキーワードCLOSの設計(1980年代後半)
defpackage:use, :export などキーワードパッケージシステムの設計

2. 4種類の「シンボルの役割」

S式の中でシンボルが使われる場面は、大きく4種類に分けられます。

2.1. 構文語(loop, declare)

マクロが展開時にシンボル名で直接照合する語で、コロンは付きません。

loopの節キーワードがその代表例です。

(loop for x in xs
      when (oddp x)
      collect x into odds
      finally (return odds))Code language: Lisp (lisp)

forinwhencollectintofinallyはどれも普通のシンボルで、loopマクロがシンボル名を文字列比較で認識しています3

declareの宣言識別子も同じ仕組みです。

(declare (special *x*))
(declare (type fixnum i))
(declare (optimize speed))Code language: Lisp (lisp)

specialtypeoptimizeにコロンはありません4

2.2. キーワード引数名

関数のラムダリストに&keyが宣言されていて、呼び出し側でコロン付きのシンボルを使います。

(make-array 10 :initial-element 0)
(open "x.txt" :direction :input)
(sort xs #'< :key #'car)Code language: Lisp (lisp)

KEYWORDパッケージのシンボルは自己評価するので、クォートなしで値として渡せます5
シーケンス関数の:test:start:endなどもここに入ります。

2.3. ラムダリストキーワード(defun, lambda)

defunlambdaの引数表に出てくる&optional&rest&key&body&wholeです。

コロンではなく&が付きます。
これらは関数呼び出し時の:fooとは別の層にあり、定義側の記法です。

(defun make-user (name &key (role :guest) (active t))
  (list :name name :role role :active active))

(make-user "Alice")
;=> (:NAME "Alice" :ROLE :GUEST :ACTIVE T)

(make-user "Bob" :role :admin :active nil)
;=> (:NAME "Bob" :ROLE :ADMIN :ACTIVE NIL)Code language: Lisp (lisp)

&keyが定義に現れると、呼び出し側では:zを使えるようになる、という関係があります6

2.4. オプション節名としてのキーワード

defclassdefpackageのように、マクロの構文上の節名そのものがキーワードシンボルになっているものです。

(defclass point ()
  ((x :initarg :x :accessor point-x :initform 0)))

(defpackage my.pkg
  (:use :cl)
  (:export :foo :bar))Code language: Lisp (lisp)

defclassのスロットオプション:initarg:accessordefpackage:use:exportはどれもキーワードです7
&key引数とは設計上の出自が異なりますが、表記は同じ形を使っています。

CLOSはANSI標準化の過程で組み込まれた比較的新しい機能です8
defclassのスロットオプションがキーワード形式なのはその時点の設計判断によるもので、loopよりも後に統合されたため、別の慣習を採用しています。

3. 判定の実用規則

まず、マクロの専用構文として節や句を読む位置では、裸のシンボルを疑います。
loopの節語とdeclareの宣言識別子がこれにあたります。

defclassdefpackageは外側の構文は裸シンボルですが、オプション部はキーワードという混在形です。

次に、仕様(HyperSpec)にそのフォームの&keyが書かれていれば、呼び出し側ではキーワード引数を使います。
make-arrayopen、シーケンス関数はこれです9

また、make-instanceに渡す初期化引数はキーワードですが、クラス名は'pointのようにクォートされた普通のシンボルで渡します。
「キーワードか否か」だけでなく、「名前をデータとして渡しているか、オプション名として渡しているか」という軸も関係してきます10

3.1. loopが際立って異質な理由

loopはInterlisp(Xeroxの研究用Lisp)が持つFOR構文にインスパイアされ、Lisp Machineグループが独自に開発しました11

設計文書には「loopのフォームはスタイライズされた英語のように見えるべき」と明記されています12
コロンなしの普通のシンボルを採用したのは、:for x :in list :collect xと書くと英文らしさが損なわれるからだと考えられます。
for x in list collect xは、確かに英語の文に近い形で、この設計方針から、loopマクロはシンボル名を文字列比較で照合する独自のパーサーを持つことになりました。

4. 統一されなかったことの意味

Common Lispは多様な背景を持つ機能群を1つの標準に収めながら、一定の一貫性を保っています。
&keyによる引数渡し機構は言語レベルで定義されており、特殊形式やマクロの構文語と明確に区別されています。

ただ、設計の時期や出自が違う機能が共存しているため、表記の規則は「覚えるもの」であって「推測できるもの」ではありません。
フォームごとに「これは関数の&key引数か、マクロの構文語か、CLOSのオプションか」を確認する習慣が必要になります13

S式という統一されたデータ表現の上に、様々な抽象をそれぞれの流儀で積み上げてきたのがCommon Lispの歴史です。
その痕跡がシンボルの表記の多様さとして残っています。

  1. 統合のきっかけはARPAマネージャーBob Engelmore主導のイニシアティブで、設計の多くはメールで行われました。Guy L. Steele Jr.が1982年のACMシンポジウムで初めて全体像を発表しています。 – Common Lisp – Wikipedia
  2. Common Lispの主要な影響元はLisp Machine Lisp、MacLisp、NIL、S-1 Lisp、Spice Lisp、Schemeの6方言です。標準化の最終文書はANSI INCITS 226-1994として1994年に公開されました。 – History – ANSI and GNU Common Lisp Document
  3. Lisp Machine Manualのloopの記述には「loop uses print-name equality to compare keywords so that loop forms may be written without package prefixes」とあります。コロンなしで書けるのはこの設計によるものです。 – Lisp Machine Manual – Loop
  4. HyperSpecではdeclareの中身を「宣言指定子(declaration specifier)」と呼び、specialtypeなどは「宣言識別子(declaration identifier)」として定義されています。これらはキーワードシンボルではなく、通常のシンボルです。 – CLHS: Symbol DECLARE
  5. HyperSpecには「Symbol tokens that start with a package marker are parsed by the Lisp reader as symbols in the KEYWORD package」とあり、KEYWORDパッケージのシンボルはすべて自己評価(self-evaluating)と定義されています。これがクォートなしで引数として渡せる理由です。 – CLHS: 11.1.2.3 The KEYWORD Package
  6. &body&wholeはマクロのラムダリスト専用で、通常のdefunでは使えません。&bodyはフォームのリストを受け取る点で&restと同じですが、pretty-printerへのヒントとして本体であることを示す意味があります。 – CLHS: 3.4.4 Macro Lambda Lists
  7. defpackageのオプション節はキーワードで始まるリスト形式です。HyperSpecのdefpackageのSyntaxには:nicknames:use:shadow:exportなどが示されています。 – CLHS: Macro DEFPACKAGE
  8. CLOSの仕様はX3J13技術文書88-002R(1988年6月)として採択されました。設計にはDaniel G. Bobrow、Linda G. DeMichiel、Richard P. Gabriel、Sonya Keene、Gregor Kiczales、David A. Moonが関わっています。CLOSはFlavors(MIT Lisp Machine)とCommonLoops(Xerox PARC)の設計を統合したものです。 – CLOS – Wikipedia
  9. HyperSpecでは&keyを持つ関数のSyntaxセクションにキーワード引数名が明示されています。たとえばmake-arrayのSyntaxは make-array dimensions &key element-type initial-element initial-contents adjustable fill-pointer displaced-to displaced-index-offset と定義されています。 – CLHS: Function MAKE-ARRAY
  10. HyperSpecのmake-instanceのSyntaxは make-instance class &rest initargs &key &allow-other-keys で、クラス名はシンボルまたは文字列のclass designatorとして渡します。 – CLHS: Standard Generic Function MAKE-INSTANCE
  11. Lisp Machine Manualには「loop was inspired by the FOR facility of CLISP in InterLisp; however, it is not compatible and differs in several details」と明記されています。InterlispのFORはWarren Teitelmanが実装した反復構文です。 – Lisp Machine Manual – Loop
  12. Lisp Machine Manualには「loop forms are intended to look like stylized English rather than Lisp code. There is a notably low density of parentheses, and many of the keywords are accepted in several synonymous forms to allow writing of more euphonious and grammatical English」と書かれています。 – Lisp Machine Manual – Loop
  13. Common Lisp HyperSpec(CLHS)はANSI Common Lisp標準のHTMLハイパーリンク版で、各フォームの正確な構文を確認する一次資料です。オンラインで無料公開されています。 – Common Lisp HyperSpec