Common Lisp の関数名には、現代の感覚からすると奇妙なものが多いです。
setq や rplacd、rempropなど、初見では意味が読み取りにくい、略語が多くあります。
この語源を辿ると、 初期 Lispは、コンスセルとシンボルのセル(値セル、関数セル、属性セルなど)という2つのデータ構造で設計され、それが抽象化されていった、という歴史が理解できます。
1. set から setq へ:quote の省略
setq の q は quote の頭文字です1。
この関数の元になったは、初期の Lisp からの set という関数です。setは、「シンボルの値セルに値を格納する」関数で、使い方はこうでした。
(set (quote x) 10)
(set 'abc 24)Code language: Lisp (lisp)
ただ、set は、第一引数を評価するので、変数名をそのまま渡したいときは quote で評価を止める必要がありました。
その変わり、setは、未定義のシンボルでも値を束縛できます。
これを毎回書くのは面倒で、 setq は「第一引数を自動的に quote する set」として生まれました。
(setq x 10)Code language: Lisp (lisp)
set と setq の違いは今でも有効です。
(setq target 'y)
;;--> caught WARNING: undefined variable: *::TARGET
(set target 42) ; y の値が 42 になる(target を評価して y を得る)
(setq target 42) ; target の値が 42 になるCode language: Lisp (lisp)
setqは、未定義のシンボルに束縛するときには、警告になりますが、そのときに定義されます。
1.1. setf:「場所」という抽象化
setq は今でも普通に使われますが、より一般的なのはsetfです。
setq が変数束縛に特化しているのに対し、setf は「場所」より一般的な概念を導入しました。
setf の f は field に由来するとされています。
ただし Common Lisp の仕様書では、「field」よりも「place」という概念で説明されます。
place とは「値を読み出せる式なら、対応する書き込み方もあるはず」という考え方で、「一般化された参照(generalized reference)」と定義されます2。
(setf x 10) ; 変数 x への代入
(setf (car cell) 'new) ; cons セルの car 部への代入
(setf (aref v i) 99) ; 配列要素への代入
(setf (getf plist :k) v) ; property list への代入Code language: Lisp (lisp)
setf の設計では、変数・consセルのスロット、配列やハッシュテーブルの要素、プロパティリストなどをすべて同じ構文で代入できます。
言語仕様(CLtL2:Common Lisp the Language 第2版)では「setf がある以上、setq・rplaca・set は冗長だが、歴史的重要性のため残されている」と書かれているように、万能な代入関数です3。
1.2. incf と decf の f:setf 系マクロの印
incf と decf の f も setf と同じ流れです。
これらは「setf できる place を更新するマクロ」で、変数だけでなく任意の place に使えます。
(incf x) ; x を 1 増やす
(incf (aref v 0)) ; 配列の 0 番目を 1 増やす
(decf (getf plist :count) 5) ; plist の :count を 5 減らすCode language: Lisp (lisp)
同系統のマクロには rotatef(複数の place の値を回転)、shiftf(値をずらす)などがあります。
名前末尾の f は「place を操作する」というシグナルになっています4。
2. rplaca と rplacd:setf 以前の世界
setf が汎用の代入関数ですが、これが一般化する前には専用の書き換え関数を使う必要がありました。
たとえば、cons セルを書き換えるには、carを書き換える rplaca、cdrを書き換える rplacd がありました。
rplaca:replace car の略で、cons の car 部を破壊的に置き換えます。rplacd:replace cdr の略で、cons の cdr 部を破壊的に置き換えます。
(defparameter cell (cons 'a 'b))
(rplaca cell 'x) ; cell => (X . B)
(rplacd cell 'y) ; cell => (X . Y)Code language: Lisp (lisp)
これは、setf ではこう書き直せます。
(setf (car cell) 'x)
(setf (cdr cell) 'y)Code language: Lisp (lisp)
rplaca と rplacd は今でも Common Lisp の標準関数として残っていますが、現代のコードでは (setf (car ...)) のほうが統一的で読みやすいため、あえて使う場面は少なくなりました5。
2.1. 使われなくなった専用の更新関数 store, fset
ほかに今は使われなくなった専用の更新関数としては、配列・文字列系では store がありました。
(store (aref v i) val)Code language: Lisp (lisp)
MacLisp での配列要素への書き込み関数でしたが、Common Lisp では (setf (aref v i) val) に置き換えられています。
また、シンボル操作系では fset がありました。
(fset 'greet (lambda (name)
(format nil "Hi, ~A!" name)))
(greet "Alice") ; => "Hi, Alice!"Code language: Lisp (lisp)
MacLisp 由来のシンボルの関数セルに関数を書き込むもので、Common Lisp では(setf (fdefinition 'foo) fn) に相当します。
まとめると、専用の書き換え関数が存在したのは、
| 対象 | 旧来の専用関数 | 今の書き方 |
|---|---|---|
| cons の car | rplaca | (setf (car ...)) |
| cons の cdr | rplacd | (setf (cdr ...)) |
| シンボルの値セル | set | (setf (symbol-value ...)) |
| シンボルの関数セル | fset | (setf (fdefinition ...)) |
| シンボルの属性セル | putprop | (setf (get ...)) |
| 配列要素 | store | (setf (aref ...)) |
Lisp が最初期に持っていたデータ構造は、cons セル、シンボルの各セル、配列です。
これらにはほぼ対応する専用関数があり、後から加わったものは最初から setf で扱う設計になっています。
3. get :シンボルのプロパティリスト操作
set は同時期の古い形式に get があります。
ただし、意味的には対称的ですが、機能的に非対称です。
setはシンボルの「値セル」を書き換えるのに対してgetはシンボルの「property list」を読みます。
対象が違うので、完全な対ではありません。
get の機能的な対は putprop です。putprop は property list に値を書き込む関数で、MacLisp 時代には get と組み合わせて使われていました。
ただし、Common Lisp ではこの putprop はなく、(setf (get sym prop) val) が使われます。
get は、シンボルそのものに紐付いた property list を読みます。
シンボルはグローバルに属性を持てる仕組みになっていて、どこからでも (get 'myvar 'type) と書けばアクセスできます。
(setf (get 'myvar 'type) 'integer)
(get 'myvar 'type) ; => INTEGERCode language: Lisp (lisp)
今では get は古風な書き方になっています6。
それは、シンボルの get はグローバルな副作用を伴うためで、構造体やハッシュテーブルに置き換えることが多いです。
3.1. getf はシンボルに限らず plistを扱う
現在では、シンボルのプロパティリストに限らず、ローカルなデータとして plist(フラットなキーと値が交互に並んだリスト)を読むのに、getfを使います。
get と getf の違いは、操作対象のデータの持ち方です。getf は任意の変数に入った plist を扱えます。
ただ、データと plist が一体化しておらず、渡す場所を明示する必要があります。
(defparameter info '(:type integer :name myvar))
(getf info :type) ; => INTEGERCode language: Lisp (lisp)
getf は、キーワード引数の処理などで今でも普通に使われます。
3.2. rempropとremf
シンボルの property list からキーと値のペアを破壊的に削除するには、remprop を使います。
(setf (get 'myvar 'type) 'integer)
(get 'myvar 'type) ; => INTEGER
(remprop 'myvar 'type)
(get 'myvar 'type) ; => NILCode language: Lisp (lisp)
remf は、getf の削除版として対応していて、任意の plist 変数からキーと値のペアを削除するマクロです。
(defparameter info '(:type integer :name myvar))
(remf info :type)
info ; => (:name myvar)Code language: Lisp (lisp)
remf は incf や rotatef と同じ f 系マクロで、place を直接操作します。
なので (remf info :type) は info という場所を書き換えます。remprop は関数、remf はマクロという違いもあります。
3.3. get, putprop, remprop
get/getf、remprop/remf の対応をまとめるとこうなります。
| 操作 | シンボルの plist | 任意の plist(例 info) |
|---|---|---|
| 読む | (get 'myvar 'type) | (getf info :type) |
| 書く | (putprop 'myvar 'integer 'type)(廃止) | (setf (getf info :type) 'string') |
| 削除 | (remprop 'myvar 'type) | (remf info :type) |
削除だけ非対称で、シンボル側には remf ではなく remprop という専用関数が残っています。remprop も (remf (symbol-plist sym) key) で代替できますが、シンボルの plist への直接アクセスは副作用が広いので、現代のコードではそもそも使う場面が限られます。
4. 名前が語ること
Common Lisp の関数名を語源から読むと、抽象化の歴史が見えてきます。
set から setq への変化は「quote を省略したい」という実用上の動機でした。
さらに、setf は「値を読み出せる場所ならどこでも書き込める」という設計の転換を体現しています。rplaca と rplacd はその統一的な抽象化が生まれる前の世界の名残です。
言語の仕様は、設計者の決断だけでなく、ハードウェアの制約、方言間の妥協、後方互換性への配慮を積み重ねてできています。
Common Lisp の関数名はその積み重ねを、5〜6文字の略語に圧縮して保存しています。
- Common Lisp HyperSpec の
setqの項では、(setq var1 form1 var2 form2 ...)のように複数の変数を一度に設定できることも定義されています。これは(setq x 1 y 2)のように使い、各 var は評価されずシンボルとして扱われます。 – CLHS: Special Form SETQ – LispWorks - Common Lisp HyperSpec (Section 5.1.1) では、place を「generalized reference(一般化された参照)」と呼び、読み取れる形式はすべて setf で書き込める場所として扱えると定義しています。 – 5.1.1 Overview of Places and Generalized Reference – CLHS
- CLtL2 の Section 7.2 には “Given the existence of setf in Common Lisp, it is not necessary to have setq, rplaca, and set; they are redundant. They are retained in Common Lisp because of their historical importance in Lisp.” と記されています。 – 7.2. Generalized Variables – CLtL2
loopマクロの展開内部でもtagbodyとgoが使われており、incfなどの place 系マクロがloopのカウンタ更新で頻繁に登場します。つまりincfは高水準なloop構文の中でも動いています。 – Common Lisp Trick #6: When Implementing Algorithms With GOTOs Consider TAGBODY – Medium- Common Lisp HyperSpec の Figure 5-1 では、
(car x)のアクセス関数に対する更新関数としてrplacaが、setfを使った更新として(setf (car x) datum)が並べて示されています。この対応表がrplacaとsetfの関係をもっとも簡潔に示しています。 – 5.1.1 Overview of Places – CLHS - CLtL2 では
putprop(シンボルの property list への書き込み関数)が Common Lisp から削除されたことに言及しており、setfを使った(setf (get sym prop) val)に統一されています。rempropは残っていますが、remfのほうが汎用的です。 – 7.2. Generalized Variables – CLtL2