princとprin1という名前は
どこから来たのか

  • prin1はLisp 1.5(1962年)から存在する出力プリミティブで、当初は「行を終端しない、atomic symbolだけを印字する」低水準な部品だった
  • princはMacLisp時代に整備された関数で、関係者の回想では “PRINt the Characters” の略とされており、周辺関数の命名パターンもC = character説を補強している
  • どちらも公式仕様に語源の明記はなく、Common Lispの設計者の一人であるKent Pitmanは「ATAN2と同様、アセンブリ言語の入口名が偶発的に残った可能性がある」と慎重に留保している

Common Lispを学び始めると、出力関数の名前で必ず戸惑う。
printprin1princの三つが並んでいる。
printはわかる。
prin11は何か。
princcは何か。
仕様書を読んでも語源は書いていない。

動作の違いは確認できる。
文字列とシンボルを渡してみると、三者の差がはっきり出る。

;; 文字列を渡した場合
(prin1 "hello")   ; => "hello"  引用符つきで出力、READ可能
(princ "hello")   ; => hello    引用符なしで出力、人間向け
(print "hello")   ; =>          改行してから "hello" 、末尾にスペース

;; スペースを含むシンボルを渡した場合
(prin1 '|A B|)    ; => |A B|    縦棒でエスケープ、READ可能
(princ '|A B|)    ; => A B      生の文字列、READ不可

;; 戻り値はいずれも引数そのもの
(prin1 42)        ; => 42  (出力も 42)
(princ 42)        ; => 42  (出力も 42、数値は同じ)Code language: Lisp (lisp)

名前はわからなくても使える。
ただ、名前の由来を知ると、この三者がどういう順番で、何のために作られたのかが見えてくる。

この疑問は2001年のUsenetスレッドでも同じように問われていて、Common Lispの仕様策定に関わったKent Pitmanが答えを探し、Maclisp開発の中心人物JonL Whiteに直接聞いた。
その回答が今も確認できる最も直接的な証言で、それでもまだ「確定した語源」とは言えない。

語源を辿るには、Lispの歴史を1958年まで遡る必要がある。


関連記事

1. マッカーシーの原論文に出力関数はなかった

1960年4月、John McCarthyはCommunications of the ACMに論文を発表した。
“Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I”、日本語にすると「記号式の再帰関数と機械による計算(第一部)」。
LISPの出発点となる論文だ1

この論文にprintという関数は登場しない。
prin1princも、もちろんない。

論文の目的は計算の形式的定義だった。
S式とは何か。
carcdrconsatomeqという基本関数とはどういうものか。
そして万能関数applyevalをどう定義するか。
真偽値はTFの二値で、入出力は完全に対象外だった。

論文に登場する基本関数はこの5つだけで、出力に関わるものは何もない。

;; 論文の5つの基本関数(Common Lispで確認)
(car '(a b c))        ; => A         先頭要素
(cdr '(a b c))        ; => (B C)     先頭以外
(cons 'a '(b c))      ; => (A B C)   先頭に追加
(atom 'x)             ; => T         アトムか
(eq 'a 'a)            ; => T         同一か

;; 真偽値は T と F(論文)、Common Lispでは T と NIL
;; 条件式もこう書かれていた
(cond ((eq 'a 'b) 'no)
      ((eq 'a 'a) 'yes))  ; => YESCode language: Lisp (lisp)

これだけで再帰関数が書けることをマッカーシーは示したが、何かを「表示する」手段はこの体系の中に含まれていない。

マッカーシーは後年の回想(1979年の”History of Lisp”)にこう書いている。
「1950年代後半は、きれいな出力や便利な入力記法は一般的に重要とは思われていなかった。
そういった処理をするプログラムは当時利用できたメモリに入りさえしなかった2」。

出力は「実装上の問題」として、理論とは切り離されていた。


2. ラッセルのevalと、同時期に書かれたREAD/PRINT

LISPの実装は1958年秋に始まった。
MIT人工知能プロジェクトでマッカーシーとマービン・ミンスキーが立ち上げたチームで、目標は最初コンパイラだった。
しかしコンパイラは「大仕事」と見なされており、まず環境を整える必要があった。

そのとき書かれたのが、リスト構造を読み書きするプログラムだ。

有名なのはスティーブ・ラッセルのエピソードで、「evalを機械語で実装できる」と気づいてインタープリタを作り上げた3
マッカーシー自身が「理論と実践を混同している、あのevalは読むためのもので計算するためのものじゃない」と言ったのに、ラッセルは論文のevalをIBM 704の機械語に落とし、バグを直して動かしてしまった。

ただ、READPRINTはラッセルとは別の仕事だった。
Lisp 1.5 Programmer’s Manual(1962年)のクレジットには明記されている。
「print and read programs were written by John McCarthy, Klim Maling, Daniel J. Edwards, and Paul W. Abrahams4」。
マッカーシー本人を含む四人が書いたのだ。

マッカーシーの回想によると、このREADPRINTのプログラムが予期せぬ副産物をもたらした。
括弧でS式を表記する外部記法——のちに「Cambridge Polish」と呼ばれるスタイル——が、READPRINTを通じて事実上の標準として固まったのだ。
「どの記法を外部形式にするかを決めたのがいつだったか、今はもう思い出せない」とマッカーシーは書いている5
出力プログラムが言語の見た目を決めた。


3. Lisp 1.5の出力体系:三つの関数と、その役割分担

1962年に刊行されたLisp 1.5 Programmer’s Manualには、出力に関係する関数が整理されている。

printpunchは、S式一個を印刷出力またはパンチカード出力する疑似関数だ。
「In each case, the print or punch buffer is written out and cleared so that the next section of output begins on a new record(いずれの場合も、バッファを書き出してクリアし、次の出力が新しいレコードの先頭から始まるようにする)」と説明されている。
一つの出力が終わったら行を終端する、という仕様だ。

prin1の説明はこうある。
prin1 is a pseudo-function that prints its argument, which must be an atomic symbol, and does not terminate the print line(prin1は疑似関数で引数を印字するが、引数はatomic symbolでなければならず、印字行を終端しない)」。

ここで重要な点が三つある。
引数はatomic symbolに限定されていること、行を終端しないこと、そしてPRINCはこのマニュアルの関数一覧(インデックス)に存在しないことだ。

Lisp 1.5の時点ではPRINCはなかった。
printprin1だけが出力プリミティブで、役割が明確に分かれていた。
printはS式一個を行ごと出力する重い関数で、prin1はatomic symbolを行末なしで吐き出す軽量な部品だった。

prin1を複数回呼ぶことで、atomic symbolを行内に並べることができた。
Lisp 1.5での典型的な使い方はこういうものだったと考えられる。

;; Lisp 1.5での想定的な使い方(prin1 + terpri の組み合わせ)
;; prin1 は行を終端しないので、続けて書ける
(prin1 'VALUE)
(prin1 '=)
(prin1 42)
(terpri)
;; => VALUE=42  (改行)

;; print は一発で行ごと出力する
(print '(a b c))
;; =>
;; (A B C)    (改行から始まり、末尾にスペース)Code language: Lisp (lisp)

なお現在のCommon Lispではprin1はS式全般を受け付け、atomic symbol限定という制約はなくなっている。
Lisp 1.5のprin1は本当に低水準な部品で、MacLispを経る中で汎用化した。

もう一つ、terpriという関数もある。
terpri prints what is left in the print buffer, and then clears it」とある。
この名前は後のEmacs Lispマニュアルに「stands for ‘terminate print’」と明記されている6
terpriの語源だけははっきりしている。


4. テレタイプ端末という物理的な制約

当時のコンピュータには画面がなかった。
出力は紙だった。

1960年代のLisp研究者が使っていたのはDEC PDP-6、PDP-10、そしてIBM 704などのマシンで、入出力端末はTeletype Model 33やModel 35のようなテレタイプ機器だった。
テレタイプはキーボードとプリンタが一体になった機械で、印字ヘッドが紙の上を走って文字を打ちつける。1分間に100文字前後しか出せない7

ここにprintが改行とスペースで終わる理由がある。
2001年のUsenetスレッドでPitmanはこう説明している。
「古い方言では改行がトークンの区切りにならなかった。
シンボルが長いと行の途中でCRLFが入り、次の行で続きが読まれた。
スペースがトークン区切りを保証した」。

JonL Whiteはさらに踏み込んで説明した。
printは「Model 33やModel 35 TTYのキャリッジリターンと紙送りが必要な複合オブジェクトにまで考慮した」関数だと。
テレタイプのキャリッジリターン(Carriage Return)は印字ヘッドを行の先頭に戻す動作で、紙送り(Line Feed)は紙を一行送る動作だ。
この二つは別々の制御信号で、プログラムが明示的に送る必要があった。

printの「改行から始まってスペースで終わる」という仕様は、テレタイプ時代の制約への対応として生まれ、60年以上経った今のCommon Lispにそのまま残っている。

現在のSBCLで確認すると、その動作は変わっていない。

;; print の動作:改行 → 出力 → スペース
(progn (print 'foo) (print 'bar))
;; =>
;; FOO
;; BAR    (各printが改行から始まり、スペースで終わる)

;; prin1 の動作:改行なし、スペースなし
(progn (prin1 'foo) (prin1 'bar))
;; => FOOBAR  (くっついて出力される)

;; だから print は複数の値を並べるとき自動で行が分かれる
;; テレタイプ時代、これは非常に便利な仕様だったCode language: Lisp (lisp)

printの前後の区切り文字は「テレタイプで長いシンボルを出力したとき行の途中でCRLFが入っても、スペースがあればトークン境界になる」という現場の知恵が埋め込まれている。


5. MacLispとPRINCの登場

Lisp 1.5を引き継ぐ形で、MIT Project MACのPDP-6でMacLispが生まれた。最初のコードベースはRichard Greenblattが書き8、その後Jon L. Whiteがメンテナンスと発展を引き継いだ。
MacLispという名前が使われ始めたのは1970年代初頭で、JonL Whiteが1970年に書いたAIM-190(MIT AI Memo No.190)には「MacLisp refers to the PDP/6 implementation of LISP at the Artificial Intelligence Group of Project MAC」とある9

MacLispではPRINCが加わり、出力関数の体系が現在に近い形に整理された。

Pitmanualと呼ばれるMacLispのリファレンスマニュアル(正式名称はThe Revised Maclisp Manual)には、PRINCPRIN1の違いがこう説明されている10

PRINC will print objects without slashification, but that output will not be suitable for being re-read by READ. The PRIN1 (and PRINT) functions will output objects with any slashification necessary for them to be later re-read by READ」。

スラッシュ化(slashification)とは、MacLispにおいてシンボルをREADで読み戻せる形にするためにエスケープ文字(当時は/、Common Lispでは\)を付けることだ。
たとえばスペースを含むシンボル|A B|PRIN1で出力すると、読み戻せる形に変換して出力する。
PRINCではスラッシュ化せず、人間が読みやすい生の文字だけを出す。

;; MacLispの例(/ がエスケープ文字)
(PRINC '|A B|)   => A B      ; スラッシュなし、人間向け
(PRIN1 '|A B|)   => /A/ /B/  ; スラッシュあり、READ可能

;; Common Lispでは
(princ '|A B|)   => A B
(prin1 '|A B|)   => |A B|Code language: Lisp (lisp)

6. Cがcharacterを意味するという傍証

PRINCCが何を意味するかについて、Pitmanualには語源は書いていない。
しかしMacLispにはC = characterと読める命名パターンが複数存在する。

EXPLODEEXPLODENEXPLODECという三つの関数がある。
EXPLODEはオブジェクトをPRIN1が出力するであろう文字のリストとして返す。
EXPLODENPRINCが出力する文字を整数(numeric)のリストで返す。
EXPLODECPRINCが出力する文字をシンボル(character)のリストで返す。

;; MacLispの例(/ がエスケープ文字)
(EXPLODE '(|A B|))   => (/( /| A | | B /| /))  ; PRIN1形式、シンボルで
(EXPLODEN '(|A B|))  => (40 65 32 66 41)         ; PRINC形式、整数で
(EXPLODEC '(|A B|))  => (/( A | | B /()          ; PRINC形式、シンボルでCode language: Lisp (lisp)

Nが numeric(整数)、Cが character(シンボル)という対比だ。
FLATSIZEFLATCにも同じパターンがある。
FLATSIZEPRIN1が出力するであろう文字数を返し、FLATCPRINCが出力する文字数を返す11
SIZEPRIN1側、CPRINC側に対応している。

Common LispにはFLATSIZEFLATCはないが、prin1-to-stringprinc-to-stringで同じ情報が得られる。

;; FLATSIZE 相当:prin1が出力する文字列の長さ
(length (prin1-to-string '|A B|))    ; => 5  (|A B| の5文字)
(length (prin1-to-string "hello"))   ; => 7  ("hello" の7文字、引用符含む)

;; FLATC 相当:princが出力する文字列の長さ
(length (princ-to-string '|A B|))    ; => 3  (A B の3文字)
(length (princ-to-string "hello"))   ; => 5  (hello の5文字、引用符なし)Code language: Lisp (lisp)

スペースを含むシンボル|A B|で差が出るのは、prin1が縦棒のエスケープを含めて5文字を返すのに対し、princは生の3文字しか返さないからだ。
FLATSIZEFLATCSIZECの対比は、この差をそのまま名前に込めている。

この命名パターンから、Pitmanは「Cはcharacterだと思う」と述べた。
ただしこれは「そう信じる」という形の推測であり、公式仕様の語源説明ではない。


7. JonL Whiteの回想と、Pitmanの慎重な留保

2001年12月、comp.lang.lispというUsenetグループでErik Naggumが質問を投げた12
「Common Lispを友人に教えていて面白い疑問が出た。
prin1という名前はどこから来たのか。princの名前はどこから来たのか」。

Pitmanが答え、JonL Whiteに聞いてみると言った13
数日後、JonL Whiteからの返答がPitman経由で投稿された。

「過年にわたって、私のprintに関する記憶は変わってきた。
しかしprinc/prin1/printのシリーズは、テキスト表現プリンタをLispで書くための段階的な試みだったことはほぼ間違いないと思う。
(1) PRINt the Characters(文字を印字する)、(2) 1つの特定オブジェクトの文字を、その同一性を可能なら保存できるように印字する、(3) 複合オブジェクトをPRINTし、旧型のModel 33や35 TTYのキャリッジリターンと紙送りの必要にまで考慮する」14

JonL Whiteの解釈では、三つの関数は段階的な設計として生まれた。

段階関数役割
1princ文字をそのまま印字(PRINt the Characters)
2prin11つのオブジェクトを、同一性を保てる形で印字
3print複合オブジェクトを、TTYの行管理まで含めて印字

しかしPitman自身は、この証言に留保を付けた。
かつてATAN2という関数の名前を聞いたとき、二引数版のATANだから2がついているのだと思った。
ところが実際にはATAN1ATAN1Aというアセンブリ言語の入口名があって、ATAN2はその偶発的な続きにすぎなかったと聞かされた。

PRIN1PRINCも同じかもしれない、と疑っている。
特別な根拠があるわけではないが、そう疑っている」とPitmanは書いた。

意図的に設計された名前なのか、それともアセンブリ言語の入口名が偶発的に残ったのか。
確かめる方法はない。


8. Common Lispへの引き継ぎ

MacLispの体系は、1984年のCommon Lisp(Guy Steele編のCommon Lisp the Language、CLtL1)にほぼそのまま引き継がれた15
1994年のANSI標準(ANSI HyperSpec、CLHS)でも同じだ。

CLHSでは三者の関係が等価式で定義されている。

(prin1 object output-stream)
  == (write object :stream output-stream :escape t)

(princ object output-stream)
  == (write object :stream output-stream :escape nil :readably nil)

(print object output-stream)
  == (progn (terpri output-stream)
            (write object :stream output-stream :escape t)
            (write-char #\space output-stream))Code language: Lisp (lisp)

writeという汎用関数に対して、prin1:escape tの、princ:escape nil :readably nilの、printは改行とスペースを伴うprin1の、それぞれ略記として位置づけられる。

面白いことに、Pitmanはこの設計についてもう一つ指摘している。
PRINT-OBJECTという名前の関数があるが、これはPRINTではなくPRIN1の挙動を実装している。本当はPRIN1-OBJECTWRITE-OBJECTと呼ぶべきだった。名前の整理が悪かった」16

実際に確認すると、print-objectは改行もスペースも付けない。
printではなくprin1の動作だ。

;; print-object は prin1 相当(改行・スペースなし)
(defclass point ()
  ((x :initarg :x) (y :initarg :y)))

(defmethod print-object ((p point) stream)
  (print-unreadable-object (p stream :type t)
    (format stream "~a,~a" (slot-value p 'x) (slot-value p 'y))))

(let ((p (make-instance 'point :x 1 :y 2)))
  (print p)    ; =>
               ; #<POINT 1,2>   (改行から始まり、スペースで終わる:printの仕様)
  (prin1 p)    ; => #<POINT 1,2>(改行なし:prin1の仕様)
  (princ p))   ; => #<POINT 1,2>(同じ、このクラスではエスケープなし)

;; print-object 自体は改行・スペースを付けない
;; 改行とスペースは print が print-object を呼ぶ「前後」に付けるCode language: Lisp (lisp)

print-objectという名前を見ると「printに対応するメソッド」と読める。
しかし実際にはprin1の挙動を提供する。
命名のねじれが、PRIN1という古い名前が引き起こした混乱の一例だ。

PRIN1という名前は古い事情で決まり、Common Lispはそれを引き継いだ。
そしてPRINT-OBJECTという新しい関数が、古いPRIN1の意味を実装した。
歴史の重なりがそのまま残っている。


9. 語源表

関数有力な語源説根拠の性格
terpri“TERminate PRInt”Emacs Lispマニュアルに明記
printそのまま。
改行+スペースはTTY時代の名残Pitman・JonL Whiteの証言
prin1低水準部品の段階番号、または「1つのオブジェクトを同一性保持で印字」Lisp 1.5の定義と整合。
JonL Whiteの回想
princ“PRINt the Characters”JonL Whiteの直接証言。
EXPLODEC/FLATCの命名パターン

10. Common Lispの「不純さ」について

printが改行とスペースで終わる。
prin1princという名前の語源が確定していない。
PRINT-OBJECTPRIN1の挙動を実装している。
こういった「ねじれ」を見ると、Common Lispが整合性に欠けると感じる人もいる。

しかしマッカーシーが1958年秋にチームでサブルーチンを手書きしてから、Lisp 1.5、MacLisp、Common Lisp、現在のSBCLに至るまで、使う人が変わり、マシンが変わり、OSが変わり、それでも出力関数は動き続けた。
printが改行とスペースで終わるのは、もうどこにも存在しないテレタイプ端末の要件だ。
prin11が何かは、その関数を最初に書いた人も今は確かめられない。

それでも関数は動く。

自然言語も同じで、語源が忘れられた言葉を日々使い、過去の慣用がそのまま生き残り、整合性より継続性を優先して変化する。
プログラミング言語にそれを求めるのは奇妙に見えるかもしれないが、60年以上にわたって人が書き、動かし、議論し続けた言語には、設計書だけでは生まれない層が積もる。

princprin1の名前の謎を追うのは、そういう層を掘ることでもある。

  1. Communications of the ACM, Vol.3, No.4, pp.184-195, DOI: 10.1145/367177.367199。タイトルに「Part I」とあるが、「Part II」はついに書かれなかった。マッカーシーは後に「Part IIは代数式への応用を扱う予定だった」と述べている。 – History of LISP (McCarthy, 1979)
  2. IBM 704の主記憶容量は最大32,768語(1語36ビット)。初期の搭載量はさらに少なく4,096語からスタートした。記憶容量に対する切実な感覚が、この発言の背景にある。 – IBM 704 – Wikipedia
  3. Steve Russell(1937年生まれ、通称”Slug”)はダートマス大学を卒業後にMITへ。後にPDP-1で世界初の広く普及したビデオゲーム「Spacewar!」(1962年)を開発した人物でもある。 – Steve Russell (computer scientist) – Wikipedia
  4. Daniel J. Edwardsはガベージコレクタと算術機能も担当。Timothy P. HartとMichael I. Levinがコンパイラとアセンブラを担当した。初期のコンパイラはRobert Braytonが書いた。 – LISP 1.5 Programmer’s Manual (MIT Press, 1962)
  5. マッカーシーはもともとFORTRANに近い「M式(M-expression)」という記法も考えていたが、結局S式が外部表記として定着した。M式は後にいくつかの処理系で試みられたが定着しなかった。 – History of LISP (McCarthy, 1979)
  6. Emacs LispではGNU Emacs Reference Manualにこの語源が記されている。terpriはCommon LispのANSI仕様でも継承され、「output streamへ改行文字を出力する」関数として定義されている。 – GNU Emacs Lisp Reference Manual: Output Functions
  7. Teletype Model 33は1963年に登場したASCII対応のテレタイプ端末で、110ボー(毎秒約10文字)で動作した。1台の価格は1,000ドル前後(当時)で、DEC PDPシリーズと組み合わせて広く使われた。後にModel 33の廉価さとASCII互換性がコンピュータ端末の標準を形成していく。LinuxのTTYという用語の語源でもある。 – Teletype Model 33 – Wikipedia
  8. Richard Greenblattは後にMIT Lisp Machineプロジェクト(1974年)を創設し、ハードウェアとソフトウェアの両方を設計した。LMI(Lisp Machines Inc.)の共同創業者でもある。 – Maclisp – Wikipedia
  9. 「MAC」はMassachusetts Institute of TechnologyのProject MACの略で、「Machine-Aided Cognition」または「Multiple Access Computer」の頭字語とされる。AppleのMacintoshとは無関係で、MacLispはMacintoshより十数年早く存在していた。 – Maclisp – Wikipedia
  10. Pitmanualの正式名称はThe Revised Maclisp Manual(通称はKent Pitmanが書いたことに由来する)。1983年のMIT AIラボ技術報告書として刊行された。現在はmaclisp.infoでオンライン閲覧できる。 – The Pitmanual: New-I/O
  11. PitmanualではFLATCFLATSIZEは「文字列幅を測る」用途で説明されている。GETCHARN(N = numeric)とGETCHAR(シンボル版)にも同じN/Cの対比がある。この一貫したパターンがPitmanのC = character説の根拠になっている。 – The Pitmanual: Character Manipulation
  12. Erik Naggum(1965-2009)はノルウェーのプログラマーで、SGML、Emacs、Common Lispの分野で知られる。comp.lang.lispには14,000件以上の投稿を残し、技術的な知見と挑発的な文体で知られた。2009年に44歳で死去。Pitmanは追悼記事を書いている。 – Erik Naggum – Wikipedia
  13. Kent M. Pitman(KMP)はX3J13のProject Editorとして、1994年に成立したANSI Common Lisp標準(ANSI X3.226:1994)の文書をまとめた。また自らHTMLに変換してCommon Lisp HyperSpec(CLHS)として公開した、仕様の実質的な主著者でもある。 – Kent Pitman – Wikipedia
  14. このスレッドはGoogle GroupsのUsenetアーカイブで現在も閲覧できる。投稿日は2001年12月9日〜11日。 – The history of print, prin1, and princ? – comp.lang.lisp
  15. Common Lisp the Language(通称CLtL1)はGuy L. Steele Jr.の編著で1984年に刊行。1986年にANSI標準化委員会X3J13が発足し、1994年12月8日にANSI X3.226:1994として正式な標準となった。CLtL1は標準化前のde facto標準として機能した。 – X3J13 – Wikipedia
  16. この指摘はcomp.lang.lispの別スレッド(princ & print-readably)でPitmanが行っている。PRINT-OBJECTprintが付けるはずの改行やスペースを付けず、prin1と同等の出力をする。命名は歴史的経緯からくる妥協だとPitmanは述べている。 – princ & print-readably – comp.lang.lisp