S式とM式

これより後ろの文章は、僕がS式やM式の理解を深めるために、AIを使って書いた。章立てや文章を調整しながら学んでいった。内容は概ね正しいと思うが、間違いや偏りもあるかもしれない。僕の足跡として残しておく。

はじめに

僕はプログラミングと技術書の翻訳を仕事にしていて、その道具として毎日Emacsを使っている。世の中ではAIの話題がこれだけ賑やかなのに、僕の方は相変わらずEmacsと睨めっこして、バッファを開き、カーソルを動かし、必要があればその場でEmacs Lispを書く。Emacs使いにはありがちなことかもしれないが、Lispは僕にとって過去の言語ではなく、日々の手元にあるごく身近な存在だ。

Lispのまわりをうろうろしていると、S式という言葉には何度も出会う。Lispの根幹的な概念だから当然だ。ところがときどき、そのかたわらにM式という言葉が現れる。S式ならわかる。M式とは何だろう。気になって調べてみることにした。

調べてみると、単なる用語の補足では済まない話だとわかってきた。M式とS式のあいだには、古い記法上の工夫以上のものがある。プログラムをどう書き、どう読み、どう扱うかという、今のプログラミングにもつながる問いが潜んでいる。この文章では、その足跡を辿ってみたい。

いま見えているLispの姿は結果であって最初からS式だけが決まっていたわけではない

M式のMは meta-expression 、S式のSは symbolic expression の略である。初期Lispでは、S式は記号式そのものを表す土台の形式であり、M式はそれらを扱う関数を人間が書きやすいかたちで表すための記法として導入されていた。S式がデータの言語で、M式はそのデータを操作するためのメタ言語として構想されていた。1

いまの僕たちはS式をLispの顔として知っている。最初から決まっていたようにも見えるが、初期の文書を辿ると、そう言い切るのは後知恵に近い。McCarthyの1960年の論文では、S式だけでなくM式も導入されていて、関数の記述はM式からS式へ翻訳されるものとして説明されている。1 LISP 1.5 Programmer's Manual でも、Section I と Section III で、S式、M式、両者の写像、universal function が初期Lispの形式体系として並べて置かれている。2

初期Lispでは、S式だけが唯一の表現として確定していたわけではない。人間が読む書き方と機械が扱う形はまだ並走していた。S式が前面に出るようになったのは、M式のリーダー(パーサ)が最終的に実装されなかったという事情が大きい。3 M式で書いても動かせないため、プログラマーはすでに動いていたS式で直接書き始めた。設計上の判断でS式を選んだというより、なりゆきで定着したというのが実態に近い。

このことは原典の並べ方を見るだけではっきりしている。 LISP 1.5 Programmer's Manual の Section III は、pure LISP の要素として S-expressions M-expressions formal mapping universal function を並べている。2 最初のLispは、S式だけで閉じた体系として説明されていたのではなく、M式からS式へ写す手続きごと含めて説明されていた。

いま標準に見えるS式も、初期にはまだ一つの候補にすぎなかった

McCarthyの論文では、S式は中核的な概念として扱われているが、「これだけがLispの書き方だ」とは言い切られていない。関数や条件分岐を人間が書くためのM式があり、それをS式へ翻訳する手順が説明に含まれている。1

S式は最初から唯一の候補ではなく、Lispを支える形式の一つとして置かれていた。いま見ているLispは、S式だけが前面に出たあとの姿であって、出発点ではない。

たとえば LISP 1.5 Programmer's Manual には、M式とS式の対応がそのまま例示されている。2

M-expression              S-expression
car[x]                    (CAR X)
ff[car[x]]                (FF (CAR X))
[atom[x]-x; T-ff[car[x]]] (COND ((ATOM X) X)
                              ((QUOTE T) (FF (CAR X))))

左側が「人間に見せたい書き方」で、右側が「機械に食わせる形」に近い。この並びを見ると、初期LispがS式だけで完結していたとは言いにくい。

初期Lispでは、人間に見せる書き方と機械が扱う形がまだ分離しきっていなかった

いまの言語では、ソースコードと内部表現のあいだに構文解析や中間表現の層が挟まる。初期Lispでは、そのあいだをどうつなぐか自体が言語の説明に含まれていた。 LISP 1.5 Programmer's Manual がM式からS式への翻訳規則を丁寧に記しているのは、その橋渡しが当時まだ言語の表層から見えていたからだ。2

この時期のLispでは、人間に自然な書き方と機械が扱える構造がまだ分業していない。どちらを前に出すかという判断が、そのまま言語の輪郭を決める問題になっていた。

McCarthyの論文も、 evalquotefn について「must be an S-expression」と書いたうえで、「Since we have been writing functions as M-expressions, it is necessary to translate them into S-expressions」と続けている。1 人間が書くものと機械が評価するものが、一つの説明の中でそのままつながっている。

採用されなかったM式を入口にすると、消えた案にもはっきりした設計思想が見えてくる

M式をただの没案として扱うと、初期Lispの輪郭は平板になる。M式には、関数や条件を数式らしく、人間に読みやすい姿で表したいというはっきりした欲求がある。McCarthyの論文でも、M式は脇役ではなく、 Another Formalism for Functions of Symbolic Expressions という節で独立して扱われている。1

消えた案を見にいく意味は、単に脇道を楽しむことにない。M式を見ると、S式が何を捨て、その代わりに何を手に入れたのかが見えやすくなる。採用されなかったものは、しばしば採用されたものの輪郭をいちばんよく照らしてくれる。

昔の分岐を辿ることで、いまのプログラミングが抱える問題も見えやすくなる

この分岐が面白いのは、古いLispの話だけでは終わらないところだ。人間に自然な表面記法を優先するか、構造の単純さと変換しやすさを優先するかという緊張は、今の言語設計にも残っている。ASTをどこまで露出させるか、マクロをどう扱うか、DSLをどの層で設計するかといった論点も、大づかみにはこの延長線上にある。

M式とS式の分岐は、Lispがまだ定まりきっていなかったことだけでなく、プログラムを読むための文として整えるか、扱える構造として揃えるかという問いが早くから露出していたことも示している。Lisp史の話であると同時に、今のプログラミングの話でもある。

M式は人間が読みやすい数式風の書き方をLispに持ち込みたいという発想から生まれた

M式を見てまず感じるのは、「人間に読ませたい」書き方だということだ。角括弧やセミコロンを使い、関数適用や条件分岐をできるだけ数式に近い顔つきで書こうとしている。S式に見慣れた目で読むと、むしろM式の方が自然に見える場面すらある。初期Lispの設計者たちが、最初から括弧だらけの形だけを考えていたわけではないことは、M式の存在だけでもよく伝わってくる。1

M式は「括弧を減らしたS式」ではない。M式には、プログラムをまず人間が読める式として整えたいという意思がある。M式が目指していたのは、内部構造をそのまま見せることではなく、構造を人間向けの見た目に包み直すことだった。

関数適用を数式に寄せて書きたいという欲求は、当時としてごく自然だった

関数の呼び出し、条件分岐、論理式をできるだけ「読む文」として自然な姿にしたいと思うのは、ごく普通の感覚だ。Lispが生まれた当時の文脈を考えれば、数式やラムダ記法に近い見た目を目指すのは自然な発想だった。

LISP 1.5 Programmer's Manual を見ると、その志向は例の並びにもよく出ている。2

M-expression              S-expression
maplist[x;f]              (MAPLIST X F)
null[x]                   (NULL X)
car[x]                    (CAR X)
eq[x;y]                   (EQ X Y)

左側は数学の関数記法に近い。 (CAR X) より car[x] の方が「関数を引数に適用している」とすぐ読める、と感じる人は多いだろう。M式は、そうした感覚を素直に取り込もうとしていた。

M式は、まず人間が読みやすい表面記法を整えようとする立場を代表していた

M式の特徴は、関数適用を角括弧で書くことだけではない。条件分岐やラムダ式まで含めて、人間が読み下しやすい姿に整えようとしている。McCarthyの論文や LISP 1.5 Programmer's Manual に出てくる条件式は、次のような形をしている。1 2

[atom[x] -> x; T -> ff[car[x]]]

この形は「条件が成り立ったらこちらへ進む」という流れが見やすい。S式に直すと、

(COND ((ATOM X) X)
      ((QUOTE T) (FF (CAR X))))

となる。M式の方が、人間が上から下へ読むための見た目を強く意識していることは明らかだ。

M式は「まず表面記法を整えたい」という立場を代表している。プログラムは人間が読むものでもあるのだから、まず人間に読める顔を与えたい。その素朴でまっとうな欲求がM式にははっきり表れている。

M式には数学記法とFORTRANの両方の影響が見えている

M式には、数学記法の影響と、当時の実用言語だったFORTRANの影響が重なっている。数学記法の側から来ているのは、関数適用、条件式、そしてラムダ記法だ。McCarthy自身も1979年の回顧で、微分プログラムを考える中で関数の引数表記や条件式やλ-notationが必要になったと書いている。3

M式では関数適用はこのように書かれる。

car[x]
eq[x;y]
maplist[x;f]

これはS式より数学の関数記法に近い。 f(x) そのものではないにせよ、関数名が先にあり引数が続く並びは、 (CAR X) より通常の数式の読み方に寄っている。

条件式も数学的な雰囲気がある。McCarthyの1960年論文の条件式は、

[atom[x] -> x; T -> ff[car[x]]]

のように書かれる。1 これはS式の

(COND ((ATOM X) X)
      ((QUOTE T) (FF (CAR X))))

より、人間が上から順に場合分けとして読むことを意識している。

一方でMcCarthyは1979年の回顧で、実装初期のM-expressionsを「FORTRANにできるだけ似せるつもりだった」と振り返っている。3 そこでは FORTRAN-like assignment statements and go tos という言い方までしている。M式は純粋に数学の記法を移してきたのではなく、当時のプログラミング言語として自然な書き方にも寄せようとしていた。

この二つの影響は矛盾していない。数学の関数記法も、FORTRAN風の書き方も、ともにS式ほど内部構造を露出させないからだ。M式は、数学とFORTRANのあいだにある初期Lispなりの折衷案だった。

M式は後から付け足された空想ではなく、Lispの初期設計で真剣に検討された道だった

あとからS式中心のLispだけを知ると、M式は「うまくいかなかった夢」のようにも見えてしまう。けれど原典を読むかぎり、そういう軽い扱いはできない。McCarthyは1960年の論文で Another Formalism for Functions of Symbolic Expressions という節を設け、M式を独立した形式として提示しているし、 LISP 1.5 Programmer's Manual でもM式からS式への翻訳規則が明示されている。1 2

M式は、Lispの歴史の余白に書かれた思いつきではない。初期Lispがどんな姿を取るべきかを考えるうえで、実際に検討されていた一つの道だった。その道が主流にならなかったからこそ、なぜこちらではなく、もう一方が残ったのかという問いを立てられる。M式には最初にきちんとした居場所を与えられていた。

S式は見た目の親しみやすさを犠牲にしてコードの構造をそのまま露出させた

M式が表面記法を整えようとしていたのに対して、S式は逆向きの道を進んでいる。S式は、関数適用も条件分岐もデータも、できるだけ同じような形で並べてしまう。見た目は無骨だし、最初の印象として「人間にやさしい」とは言いにくい。けれどその代わりに、プログラムの構造がほぼそのまま露出する。

たとえば、M式では

ff[car[x]]
[atom[x] -> x; T -> ff[car[x]]]

のように、関数適用と条件分岐がそれぞれ別の顔をしていた。1 これに対してS式では、

(FF (CAR X))
(COND ((ATOM X) X)
      ((QUOTE T) (FF (CAR X))))

となる。どちらも「一つの式の中に別の式が入れ子になっている」だけであり、S式はその事実をあまり隠さない。見た目の親しみやすさより、構造の一貫性を優先しているのである。

S式では表面の書き方を削ることで、内部表現とのずれがほとんど消えていく

S式の大きな特徴は、表面の書き方と内部の構造が近いことだ。M式では、人間に見やすそうな書き方をまず用意し、それをS式へ写すという段差がある。S式だけを使うなら、その段差はほとんどなくなる。書かれている形が、そのまま木構造やリスト構造として読めるからだ。

LISP 1.5 Programmer's Manual でも、M式からS式への翻訳規則は機械的に与えられている。2 逆に言えば、その翻訳先であるS式の方は、評価系が扱う形式に近い。見た目を整えるための飾りを削ることで、S式は内部表現とのずれを小さくしている。

この性質は読者にとって必ずしも親切ではない。けれど言語や処理系の側から見ると強い。どの式もだいたい同じような入れ子の形に落ちてくるからだ。

S式の強みは、コードをそのままデータに近い形で置けるところにある

S式が「括弧の多い記法」で終わらないのは、コードをそのままデータに近い姿で置けるからだ。関数呼び出しも、条件分岐も、リストとして書かれた式の一種として並ぶ。プログラムの一部を取り出したり、組み替えたり、別の式として埋め込んだりするとき、ソースコードとデータ構造のあいだの距離が短い。

McCarthyが1960年論文で evalquoteeval を書くために必要だったのも、まさにこの近さだった。1 関数をS式として表現できるからこそ、関数をデータとして渡し、それを別の関数が評価するという構図が取りやすくなる。コードが単なる文字列ではなく、扱える対象になっている。

この近さは、あとで同図像性やマクロの話につながっていくが、出発点ではもっと素朴だ。S式では、プログラムがまず構造物としてそこにある。そのこと自体が、M式と大きく違う。

書き方と内部表現の距離が短いからこそ、読むことと操作することが接続される

表面記法と内部表現の距離が短いと、プログラムを「読む」ことと「操作する」ことが離れにくくなる。ある式がどこで終わるか、どこに別の式が入れ子になっているか、何を一単位として取り出せるかが、見た目でも比較的はっきりしているからだ。S式が最初はとっつきにくく見えても、慣れると式の境界を手で触れるような感覚が出てくるのはこのためだ。

この性質は、後のEmacs LispやLisp系開発環境で具体的な力になる。式単位で移動し、選択し、その場で評価し、書き換えられるという感覚は、S式がプログラムの構造を露出させているからこそ成り立つ。読みやすさのために表面を整えたM式とはちがって、S式は最初から「読むこと」と「いじること」が接続しやすい場所に立っていた。

S式の無骨さは、単なる不便さではない。人間向けの親しみやすさをある程度犠牲にしてでも、構造がそのまま見えることを選んだ結果としての無骨さである。その選択があとで大きな力を持つことになる。

Lispの核にあるのは、記号式を対象にした計算と評価の仕組みだ。S式は、その本質を表に出し、扱いやすく保ち、コードとデータの距離を極端に近づけるための強力な表現形式だった。だからこそS式はLispそのものではなくても、Lispの歴史の中で決定的に重要だったのである。

S式は構造を露出させることで同図像性や変換可能性の土台にもなった

S式の重要さは、読みやすさを削って構造を露出した、というところで終わらない。構造がそのまま見えていることは、コードを別のコードの材料として扱いやすいということでもある。プログラムを読む、組み立てる、変形する、評価するといった操作が、別々の層に分断されにくくなる。あとで「同図像性」や「マクロ」といった言葉で語られる特徴も、出発点ではこの近さに支えられている。

S式それ自体が魔法のようにすべてを生み出すわけではない。Lispの本質は、記号式を対象にした計算と評価の仕組みにある。ただ、その本質が自分自身を対象に取りやすくなるための足場として、S式は異様にうまくできていた。だからS式を見ていると、単なる記法の選択だったはずのものが、その後の言語機能や開発文化の条件にまで食い込んでいることがわかる。

同図像性の核心は括弧の見た目ではなく、コードとデータが近いことにある

同図像性という言葉を出すと、「Lispは括弧で書くから特別だ」という印象が先に立ちやすい。けれど本当に重要なのは括弧の見た目ではなく、コードとデータの距離がきわめて短いことだ。プログラムがリストや記号式として見えていて、その形を言語の内側からそのまま扱えるからこそ、コードを別のコードの入力にしたり、コードを組み立てるコードを書いたりしやすくなる。

たとえば、

(CONS 'CAR '(X))

のような式から、

(CAR X)

に対応するリスト構造を作れる、という感覚はLisp的だ。プログラム断片が最初からリストとしてそこに見えている。この近さこそが、同図像性の核にある。

S式そのものが同図像性なのではなく、同図像性を成立させやすい土台になっている

S式そのものが即座に同図像性なのではない。S式はあくまで表現形式であって、同図像性は言語全体の性質だ。プログラムがどう読まれ、どう評価され、どこまで自然にデータとして持ち回れるかまで含めて、はじめてその性質が立ち上がってくる。

それでもS式が決定的に重要なのは、その性質を成立させやすい土台になっているからだ。表面記法と内部表現の距離が近く、式の主な形が均一だから、コードをデータとして渡したり、部分式を取り出したり、別の式へ埋め込んだりすることが無理なく見える。表面記法がもっと豊かで不均一だったら、同じことをするたびに別の変換層が必要になっていただろう。

マクロやコード変換のしやすさは、S式が作るこの近さの上に立っている

コードとデータの距離が短いと、プログラムの変換が扱いやすくなる。ある式を別の式へ写す、共通のパターンを抽象化する、新しい構文に見えるものを既存の式へ展開するといった操作が、文字列の書き換えではなく、構造の書き換えとして見えるからだ。マクロやコード生成がLispで早くから強い力を持ったのも、この基盤があったからだろう。

S式がマクロを自動的に発明するわけではない。けれどマクロのような仕組みが自然に見える地盤は作っている。入力も出力も式であり、その式がそのままデータ構造として見える以上、変換は素直な操作になる。S式は「書きにくいが整然とした記法」なのではなく、「変換しやすい記法」でもある。

このことは現代の言語でも別の形で繰り返されている。ASTを組み立てて変換するツール、構文木に対して動くフォーマッタやリファクタラ、マクロやDSLの仕組みは、どれもコードを構造として扱うことの上に立っている。S式はその構図を、早い時期に、露骨なかたちで表に出していた。

M式とS式の分岐を辿るとプログラムを文章とみなすか構造とみなすかの違いが見えてくる

ここまで見てきたM式とS式の違いは、単なる記法の趣味の問題ではない。引いて見ると、プログラムを何として扱いたいのかという違いにつながっている。人間が上から下へ読み下すための文として整えたいのか、それとも分解され、組み替えられ、評価される構造として揃えたいのか。M式とS式の分岐は、その二つの方向が早い段階で分かれていたことを示している。

実際のプログラムはそのどちらでもある。人間が読めなければ困るし、機械が扱えなければ動かない。けれど、どちらを前面に出すかで、言語の顔つきも、そこから育つ文化も変わってくる。M式とS式の選択が後になって曖昧に溶けたのではなく、最初から露骨なかたちで姿を見せているところが面白い。

M式は読みやすい文としてのプログラムに寄り、S式は操作できる構造としてのプログラムに寄った

M式にあるのは、まず人間が見て理解しやすい形に整えたいという意志だ。関数適用、条件式、ラムダ記法を、従来の数学記法や当時のプログラミング言語に近い顔つきで書こうとしている。プログラムは「読むための文」に近い。

S式は、読むための表面を整えることより、式がどう入れ子になっていて、どこからどこまでが一単位なのかをそのまま見せる方へ寄っている。プログラムは文というより、操作できる構造物として目の前に置かれる。どちらが優れているかというより、何を先に満たそうとしているかが違う。

人間が読みやすい記法と、機械が処理しやすい記法はしばしば一致しない

人間に自然な記法と、機械が確実に扱いやすい記法はしばしば一致しない。人間は、見た目の変化や文脈の違いをうまく飲み込みながら読む。機械にとって扱いやすいのは、均一で、曖昧さが少なく、構造が露出した表現だ。

M式とS式の違いは、そのずれがどこにあるかを目に見えるかたちで示している。M式は人間の読む流れに寄ろうとし、S式は処理系が扱う構造に寄ろうとした。言語設計における多くの悩みは、この二つをどう折り合わせるかにある。

Lispが面白いのは、そのずれを隠さず前面に出したところにある

多くの言語では、このずれは表面上あまり見えないよう工夫される。人間向けの豊かな構文を与え、その裏で構文解析や中間表現への変換を処理系が引き受けるからだ。それはそれで合理的であり、現代の言語の多くはその方向を取っている。

LispはS式の系統では、そのずれをあまり隠さなかった。プログラムはきれいに文章らしく見えるものではなく、最初から構造として目の前に出てくる。見た目の親しみやすさは損なわれるが、代わりにプログラムを構造として扱う感覚が早い段階で露出する。Lispの記法がいまでもしばしば「奇妙」に見えるのは、この露出のせいでもある。

S式が残ったのは読みやすさを諦めたからではなく、別の合理性を選んだからだった

S式が残ったことを「読みやすさを諦めた結果」とだけ見るのは少し違う。ただし、S式の定着には意図的な設計判断というより、M式のリーダーが実装されなかったという偶発的な経緯があることは念頭に置いておく必要がある。それでも、S式が定着したあとの歴史を振り返ると、構造の均一さ、内部表現との近さ、コードをそのまま扱えること、変換しやすさといった別の合理性が、言語の寿命や拡張性、開発環境との結びつきをたしかに下支えしてきた。

M式とS式の分岐は、見た目の好みをめぐる小さな逸話ではない。プログラムを何として扱うのかという問いに対して、Lispが早い段階で一つの答えを選んだ瞬間として読める。その答えがその後どれだけ長く尾を引いたかは、現代の言語設計や開発環境を見てもわかる。

REPLや対話的開発の作法にもコードを構造として扱いやすいことの延長線が見える

M式とS式の分岐がその後どこまで尾を引いたかを見るとき、言語仕様だけを見ていても足りない。もう一つ大きいのは、プログラムとどう付き合うかという作法の違いだ。Lispの系譜では、コードをその場で読み、評価し、書き換え、また評価するという対話的な作業が早くから強い位置を占めていた。REPLそのものはS式だけから生まれたわけではないが、コードを構造として扱いやすいことが、この作法を支えやすくしていたのは確かだ。

対話的開発は単に「すぐ実行できて便利」という話ではない。書いたものをそのまま式として渡し、その一部を取り出し、別の形にして、動いている系の中で確かめる。その往復が自然に見えることが大きい。S式は表面記法と内部表現の距離が短いぶん、この往復を支える土台になりやすかった。

InterlispやLisp Machineの文化には、対話しながら少しずつ書き換える作法が早くからあった

McCarthyは1979年の History of Lisp で、Lispはtime-sharing環境と相性がよく、関数を定義し、試し、書き直し、また試すことが自然だったと回顧している。3 プログラムは一度書いて終わる文書ではなく、動いている系の中で少しずつ育てていく対象になる。

その後のInterlispやLisp Machineの文化では、この感覚がさらに濃くなる。実行中のシステムを止めずに関数を差し替える、環境の中で定義を見直す、動いているものに手を入れながら少しずつ前に進む。ここではプログラムは完成したテキストというより、つねに触り続ける構造物に近い。REPLや対話的開発の作法は、そうした文化の中で強く育っていった。

Common LispのSLIMEやClojureのCIDERは、その作法を現代の環境へ持ち込んでいる

Common LispのSLIMEやClojureのCIDERに触れると、この古い作法がそのまま残っているのがわかる。式をその場で送って評価する。関数を定義し直す。部分式だけを試す。結果を見ながら少しずつ形を変えていく。ファイルを保存して一括でビルドする流れではなく、動いている系と対話しながら進める流れが前面に出る。

これを可能にしているのはS式だけではない。実行系、環境、ツールの設計が大きい。それでも、コードが構造として見えていて、一部分を切り出したり、そのまま別の式として送ったりしやすいことは、この作法を支えやすくしている。SLIMEやCIDERが特別なのは、古いLispの対話性を現代のエディタと実行環境の接続の中で高い解像度で生き残らせているところにある。

構造を操作しやすい表現は、対話的な開発体験を支える土台にもなる

対話的開発の面白さは、実行が速いことだけではない。プログラムの一部を、無理なく一単位として取り出せることが大きい。どこからどこまでが一つの式なのか、どの部分を評価対象として送ればよいのか、どの構造をいま書き換えているのかが見えやすいと、試すことと直すことの距離が縮まる。

S式は、この意味で対話的な開発体験を支える土台にもなっている。コードが最初から構造として目の前にあり、その構造が比較的均一だから、部分式の切り出しや再評価が素直に見える。あとでEmacs Lispの節で見るように、式単位で移動し、そのまま評価できる感覚は、この延長線上にある。REPLや対話的開発の作法は、単にLisp文化の偶然の癖ではなく、コードを構造として扱いやすいことから自然に育った側面を持っている。

M式とS式の古い分岐はいまの言語設計でも表面構文と内部構造の緊張として生き残っている

M式とS式の分岐が面白いのは、昔のLispだけの事情に見えて、実は広い問題を先取りしているところだ。人間にとって自然な表面構文を与えたい。しかしその一方で、処理系やツールにとっては、均一で扱いやすい内部構造がほしい。この二つの要求はいまもきれいには一致しない。現代の言語設計は、このずれをどう飼いならすかの工夫だと言ってよい。

現代の言語がS式を採用していないからといって、M式とS式の問題が消えたわけではない。多くの言語は、M式的な表面の豊かさを取り込みつつ、その裏側でS式的な均一性に似たものをASTや中間表現として作り直している。昔のLispでむき出しだった緊張は、いまでは別の層に移っているだけかもしれない。

現代の言語も、表面構文をどこまで豊かにし内部表現とどう折り合うかを悩み続けている

現代の言語はたいてい、Lispほど露骨には構造を見せない。その代わり、人間にとって読みやすい表面構文を豊かに作り込む。演算子の優先順位、文の区切り、糖衣構文、型注釈、ジェネリクス、メソッド呼び出し、内包表記など、見た目の自然さや簡潔さを支える工夫はいくらでもある。

けれど、その豊かさは無料ではない。表面構文が豊かになるほど、処理系はそれをいったん分解し、より均一な内部表現へ落とし込まなければならない。M式的な見え方を前面に出せば出すほど、どこかでS式的な単純さに相当するものを裏側に用意する必要が出てくる。現代の言語設計は、この折り合いをどこでつけるかをずっと悩み続けている。

ASTと構文変換の文化は、S式が露出させた問題を別の技術で扱い直している

その折り合いのために、いまの言語やツールがしばしば頼るのがASTだ。ソースコードをいったん構文木に落とし、その木に対して解析、最適化、フォーマット、リファクタリング、コード生成を行う。ここではプログラムは文章というより構造物として扱われている。

この構図は、見た目こそLispと違っていても、S式が露出させていた問題と近い。コードをどう構造として捉えるか、どこまで機械的に変換可能にするか、という問題だ。Lispではその構造がソースにほぼ露出していたが、現代の言語ではASTという裏側の層に退いている。違うのは見せ方であって、問題そのものは連続している。

マクロとDSLの設計では、言語をどこまで開いてよいかという同じ問いが繰り返されている

マクロやDSLの設計になると、この緊張はさらに直接的に現れる。新しい記法や抽象化を導入したいが、言語全体の一貫性や処理系の単純さは壊したくない。書きやすさを増やせば、内部の規則や変換の仕組みは複雑になりやすい。構造の均一さを守れば、利用者の側には無骨さが残る。

M式とS式の分岐は、まさにこの問いの古い原型として読める。どこまで人間向けの表面を与えるのか。どこまで内部構造をそのまま見せるのか。現代のマクロやDSLは、この問いにそれぞれ別の答えを与えているにすぎない。だからM式とS式の話は、古いLispの逸話で終わらず、いまでも言語を設計したり拡張したりする場面で、形を変えて何度も現れる。

Emacsの外へ目を向けると現代のエディタは別の仕方で拡張性と対話性を実現している

VS Code、Cursor、Neovim、IntelliJは、どれも強い拡張性を持っているし、対話的に作業できる場面も多い。ただし、その実現の仕方はかなり違う。Emacs Lispが特別なのは、エディタの内側にそのまま言語が住み着き、利用者が直接その内部へ入り込めることだった。現代のエディタは、そこを別のアーキテクチャで実現している。

比較するときの問いは、「Emacs Lispと同じかどうか」ではなく、「どこまで道具の内側へ入り込めるのか」「コードは構造としてどれだけ露出しているのか」になる。M式とS式の話で見えてきた表面と内部構造の緊張は、ここでも別のかたちで現れている。

VS CodeやCursorでは、拡張性は強いが、エディタの内部へ入るというより拡張ホスト越しに接続する

VS Codeの拡張モデルは非常に強力で、言語サポート、UI、デバッガ、Webview、AI機能まで広く拡張できる。けれど、その仕組みはEmacs Lisp的ではない。公式ドキュメントが述べているように、拡張は Extension Host で動き、UI本体とは分けられている。4 安定性と性能のために、拡張がUIを直接書き換えたり、無制限に内部へ入り込んだりしないよう設計されている。

このモデルでは、拡張性はあるが、利用者がエディタそのものの実行環境へその場で入り込む感覚は薄い。コードはAPI越しにエディタへ働きかけるのであって、編集器の中核を同じ言語でそのまま書き換えるわけではない。CursorもこのA系譜にあり、公式ドキュメントはCursorがVS CodeのコードベースA上に構築されていて、設定や拡張機能を移行できると説明している。5 CursorはAIで大きく拡張されていても、拡張モデルの地盤はVS Code的だ。

NeovimはLuaを内蔵するぶんEmacsにかなり近づくが、それでも同じではない

Neovimはこの中では面白い位置にいる。公式ドキュメントによれば、LuaエンジンはAbuilt-in and always availableAで、Luaのユーザ設定やプラグインは自動的に発見されて読み込まれる。6 7 利用者は直接エディタの中へコードを書き込める。 plugin/foo.lua を置けばそれがそのままプラグインになるという感覚は、Emacs Lispに近い。

ただしNeovimがEmacs Lispと同じだとは言いにくい。Neovimの中核はCとAPIにあり、Luaは強力な埋め込み言語だが、エディタ全体が最初からLuaで書かれているわけではない。利用者は豊かなAPIを通じて中へ入れるが、Emacs Lispのように「エディタの多くがその言語でできていて、同じ言語でその場で差し替える」という一体感とは少し違う。

それでもNeovimがEmacsに近いのは、コードを書いてすぐ効かせること、設定と拡張の境界が薄いこと、そしてLuaが日常的な操作の中に深く入り込んでいることだろう。現代のエディタの中では、Emacs Lisp的な感覚を別の言語で強く残している例だ。

IntelliJ系では、構造はむしろPSIの側に強くあり、拡張性は豊かでもLisp的ではない

IntelliJ系のIDEは拡張性という意味で非常に強力だ。公式のPlugin SDKは、IntelliJ Platformの本当の力は Program Structure Interface (PSI) にあり、そこから構文モデル、意味モデル、索引、ナビゲーション、補完、検査、リファクタリングなどが支えられていると説明している。8 コードは構造として深く扱われている。

ただし、その構造はS式のように表面へ露出しているのではなく、洗練された内部モデルの側にある。利用者やプラグイン作者は、そのモデルに対して豊かなAPIで接続する。これは非常に強力だが、Emacs Lisp的というよりは、巨大で精密な構造体にプラグインで接続する感覚に近い。構造の力は強いが、それは利用者の日常的な「式をその場で触る」感覚としてはあまり現れない。

IntelliJは、S式的な露出を選ばず、M式的な表面の豊かさと裏側の構造モデルの強さを両立させようとする現代的な答えの一つだと言える。

こうして並べると、Emacs Lispの特異さは拡張性の強さよりも拡張性の位置にある

現代のエディタはどれも強い拡張性を持っている。VS CodeとCursorは拡張ホスト越しに、Neovimは埋め込みLuaを通じて、IntelliJはPSIとプラグイン基盤を通じて、それぞれ豊かな内部構造へ接続している。「いまのエディタは拡張できない」のではまったくない。

それでもEmacs Lispがまだ特別に見えるのは、拡張性の強さそのものより、拡張性の位置のせいだ。エディタの外側からAPIで接続するのではなく、道具の内側にそのまま言語が住んでいて、その場で読み、書き、評価できる。この近さは、M式とS式の話で見てきた「コードを構造として扱いやすいこと」が、ほとんど生活の手つきにまで落ちてきた例として極端だ。現代のエディタを見渡すと、その極端さがむしろよくわかる。

Emacs Lispを見るとS式の考え方は現在でも道具の内側で生きた実践になっている

ここまでの話を手元にまで引き寄せて考えるなら、やはりEmacs Lispがいちばんわかりやすい。Emacs LispはLispの歴史の中では特殊な方言の一つだとも言える。けれど、S式が単なる記法ではなく、コードを構造として扱う感覚そのものを日常の道具の中に住まわせるという点では象徴的な存在だ。プログラムはエディタの外にある対象ではなく、エディタそのものの内側にもあり、必要があればその場で読み、直し、評価できる。

GNU Emacsの歴史を見ると、ここで生きているのは単にLispという名前だけではない。S式が持っていた、構造の境界が見えやすいこと、式をそのまま渡したり評価したりしやすいこと、コードとデータの距離が短いことが、編集器の文化にまで降りてきている。Emacs Lispを見ると、M式とS式の分岐が言語仕様の中だけで終わらず、道具との付き合い方そのものに食い込んでいたことがよくわかる。

Guy Steeleを含む初期Emacsの系譜を辿ると、Lisp的な拡張可能性はかなり早い時期から育っていたとわかる

Emacsの前史を辿ると、拡張可能性そのものはGNU Emacsから突然現れたわけではない。HOPLの Evolution of Emacs Lisp によれば、初期のEmacsはTECO上のマクロとキーバインドの集合として始まり、Guy Steeleが利用者のあいだの共通のマクロ群をまとめ、Richard Stallmanがそれを最初のEmacsへ発展させたという系譜を持っている。9 編集器を利用者が自分で書き換えるという感覚は、早い段階から育っていた。

GNU Emacsで特別なのは、その拡張可能性がLispという形で一段深く組み込まれたことだろう。単なるマクロの寄せ集めではなく、編集器全体のふるまいを同じ言語で書き、読み、評価できるようになった。S式の構造を日常の操作単位として持ち込めたことも、この拡張可能性を支えたはずだ。

Richard StallmanがGNU Emacsで選んだのは、利用者が道具の内側へ入り込めるLispだった

Richard StallmanはGNU Emacsを作るにあたって、利用者が外から完成品を使うだけではなく、道具の内部に入り込めることを強く重視していた。本人の回顧でも、Mocklispでは programs were not data であり、それがLispの重要な力を欠いていたと述べている。10 GNU Emacsで採られたのは、編集器を拡張するためのちょっとしたマクロ言語ではなく、利用者が自分の道具を書き換えられるLispだった。

この選択は、単に「Emacsが拡張しやすい」というだけではない。編集器のコマンド、設定、振る舞いが、同じ言語の同じ式として扱える。コードはシステムの外で書かれたものではなく、システムの内側で生きている。S式が持っていた「構造として扱いやすいコード」という性質が、具体的なかたちで道具に入り込んでいるのである。

GNU Emacsでは、編集環境と実行環境が分かれず、利用者がその場で環境を書き換えられる

GNU Emacsでは、コードを書く場所とそのコードが効く場所がほとんど地続きになっている。関数を書き、バッファの中で評価し、その結果をすぐにいま使っている編集器に反映させることができる。設定を書き換えるというより、その場で環境のふるまいを差し替える感覚に近い。

この近さはLispの対話性そのものでもあるが、同時にS式の構造の均一さにも支えられている。式の境界が見え、どこまでを評価単位として送るかが明確だからこそ、編集環境と実行環境が分離しきらない。「その場で書いて、その場で効く」という感覚は、Emacs Lispの大きな魅力の一つだ。

Emacs Lispの身近さは、S式の構造が日常的な操作の単位になっていることに支えられている

Emacs Lispが身近に感じられるのは、単に設定ファイルに少し書くからではない。S式の構造が、ふだんの操作の単位としてそのまま現れているからだ。式の先頭へ移動する、次の式へ進む、前の式に戻る、式を評価する、といった動作が自然に一続きになっていると、S式は抽象的な概念ではなく、毎日手で触るものになる。

Emacs Lispは、S式を「読むための対象」としてだけではなく、「編集し、試し、確かめる対象」として日常化している。S式の境界が見えることが、そのまま操作しやすさに接続しているのである。前の節で見た「読むことと操作することの接続」が、そのまま生活の手つきとして現れている。

Lispの閉じカッコが重要なのは、構文の終わりがそのまま移動と評価の単位になるからである

Lispの閉じカッコは、見た目のうるささとして語られがちだが、Emacsの中ではまったく別の意味を持つ。式の終わりが閉じカッコとしてはっきり見えることで、どこまでが一つの評価単位なのかがその場でわかる。読みやすいとか読みにくいとかいう話ではなく、構文の境界がそのまま操作の境界になるという話だ。

この性質があるから、式の末尾にカーソルを持っていってそのまま評価する、あるいは一つの式をまるごと飛び越えて移動するといった動作が自然に成立する。閉じカッコの連なりは、S式の無骨さの象徴であると同時に、その無骨さが具体的な力に変わる地点でもある。

GNU Emacsの対話性は、編集中の式をその場で評価できることによって具体的な体験になる

GNU Emacsの対話性が抽象的な理念で終わらないのは、編集中の式をその場で評価できるからだ。バッファの中の一式を選んで試す、末尾にカーソルを置いて評価する、結果を見てすぐ書き換える。この往復が速く、しかも同じバッファの中で完結するので、プログラムは完成品というより会話の相手に近いものになる。

S式の構造が明確でなければ、この体験は不安定になるだろう。どこまでを送るのか、どこが一単位なのかが曖昧なら、その場で評価すること自体がやりにくい。GNU Emacsの対話性は、Lisp一般の伝統とともに、S式の構造の見えやすさにも強く支えられている。

閉じカッコの連なりが見せるのは、S式が編集しやすいだけでなく確かめやすい形でもあるという事実である

たとえば (+ 1 (- 2 (* 3 4))) のような式では、末尾に並ぶ閉じカッコを見れば、その場がいくつかの入れ子の終点であることがすぐわかる。そこでカーソルを置き、必要なら一つ移動して評価する。そうすると、いま自分がどの式を相手にしているのかを、構文の形そのものが教えてくれる。閉じカッコは単なる記号の騒がしさではなく、構文境界と評価境界を揃えるための手がかりになっている。

S式は編集しやすいだけでなく、確かめやすい形でもある。プログラムの一部を読むこと、そこへ移動すること、その場で評価することが一続きになる。Emacs Lispを見ると、S式の無骨さが実践的な強みへ変わっていることがよくわかる。

おわりに

ここまでS式とM式について、しつこいくらいに辿ってきた。おかげで全体像やその経緯もおぼろげながら見えてきた。最後に、ここまで見てきたことを少しまとめておきたい。

M式を入口にすると、S式の背後にある設計上の選択が見えてくる

M式とS式を辿っていくと、Lispの記法は最初から一つに定まっていたわけではなく、初期には少なくとも二つの方向が見えていたことがわかる。M式は、より数式に近く、人間が読みやすい書き方を目指していた。S式は、見た目の自然さよりも、構造の単純さと扱いやすさを優先した表現だった。どちらが「きれい」かという好みの問題ではなく、プログラムをどういうものとして扱いたいかという違いが、そこにははっきり現れている。

S式が残した考え方は、いまの言語や開発環境にも別の形で残っている

S式が重要なのは、単にLisp特有の見た目をしているからではない。コードとデータの距離を縮め、同図像的な性質を支え、構文の解析や変換や拡張を容易にする土台になったからだ。LispにおけるS式の定着は、M式のリーダーが実装されなかったという偶発的な事情に負う面が大きい。それでもS式が長く使われ続けたのは、読みやすさを軽んじたからではなく、構造を露出させることで得られる力がその後の発展を実際に支えたからだろう。M式とS式の分岐は、プログラムをまず人間が読む文章として整えたいのか、それとも機械的に扱える構造として揃えたいのかという、今の言語設計にも続く問いを早い段階ではっきり見せている。

その影響はLispの歴史の中だけに閉じていない。ASTやマクロやDSLの設計、さらにREPLを中心にした対話的な開発環境の思想にも、その延長線を見ることができる。Emacs LispやSLIMEやCIDERのような環境で、式を単位に移動し、その場で評価し、少しずつ書き換えて確かめていける感覚は、その一つの具体例だ。S式の選択は、記法の表面だけでなく、プログラムとの付き合い方そのものを形作ってきた。

最後に僕自身の感触を付け加えるなら

僕自身は、M式を知ればS式の輪郭も少し見やすくなるだろう、くらいの気持ちで調べ始めた。けれど実際には、記法の違いを追うことが、プログラムは何でできていて、僕たちは何を読んで何を操作しているのかを考え直すことにつながった。S式を好きになるかどうかは人それぞれでよい。それでも、なぜあの形が生まれ、なぜ残ったのかを辿ることは、今のプログラミングを少し違う角度から見直すきっかけにはなる。

さらに辿っていくなら

この記事では、僕自身が気になって辿り始めた道筋をひとまずまとめてみたが、ここから先はやはり原典にあたるのがいちばん面白い。McCarthyの論文のような一次資料を読むと、M式とS式がどんな文脈で構想されていたのかが生のかたちで見えてくるし、Lisp史やEmacs Lisp史を辿る資料を合わせて読むと、その選択が後の開発環境やプログラミングの作法にどうつながっていったかも見えやすくなる。もしこの記事で同じ引っかかりを覚えたなら、参考文献の節を次の入口として使ってもらえればうれしい。

参考文献と関連資料

一次資料

歴史を俯瞰する資料

Emacs Lispをたどる資料

現代の言語設計につなぐ資料


1

John McCarthy, Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I (1960). M-expressions と S-expressions の両方が導入され、関数をM式からS式へ翻訳して evalquote に渡す構図が示されている。 https://www-formal.stanford.edu/jmc/recursive/recursive.html

2

LISP 1.5 Programmer's Manual (1962), Section I および Section III. "pure LISP" の要素として S-expressions, M-expressions, formal mapping, universal function が並べて説明されている。 https://softwarepreservation.computerhistory.org/LISP/book/LISP%201.5%20Programmers%20Manual.pdf

3

John McCarthy, History of Lisp (1979). M-expressions が "intended to resemble FORTRAN as much as possible" だったこと、またM-notationが厳密には定義されないまま後景に退いたことを回顧している。 https://worrydream.com/refs/McCarthy_1979_-_History_of_Lisp.pdf

4

Visual Studio Code Docs, Extension Host. 拡張が Extension Host 上で動き、UI 本体と分離されていること、安定性と性能のための制約が説明されている。 https://code.visualstudio.com/api/advanced-topics/extension-host

5

Cursor Docs, VS Code. Cursor が VS Code のコードベース上に構築されており、設定や拡張機能を移行できることが説明されている。 https://docs.cursor.com/ja/guides/migration/vscode

6

Neovim Docs, Lua. Lua engine が組み込みで常時利用可能であり、Lua を通じて API やエディタ機能へアクセスできることが説明されている。 https://neovim.io/doc/user/lua

7

Neovim Docs, Lua-plugin. plugin/ 以下の Lua ファイルが自動的にプラグインとして読み込まれること、登録手続きなしにプラグインを書けることが説明されている。 https://neovim.io/doc/user/lua-plugin/

8

JetBrains, The IntelliJ Platform. IntelliJ Platform の PSI が構文・意味モデル、索引、補完、リファクタリング等の土台になっていること、プラグイン基盤が豊富であることが説明されている。 https://plugins.jetbrains.com/docs/intellij/intellij-platform.html

9

Stefan Monnier and Michael Sperber, Evolution of Emacs Lisp (2020). TECO 上の Emacs から GNU Emacs と Emacs Lisp への系譜、設計動機、後年の発展が詳しい。 https://www.deinprogramm.de/sperber/papers/hopl-4-emacs-lisp.pdf

10

Richard Stallman, My Lisp Experiences and the Development of GNU Emacs. Mocklisp の限界や、GNU Emacs で real Lisp を選んだ理由を本人が振り返っている。 https://www.gnu.org/gnu/rms-lisp.html.en

作成日