- Lispの核心は括弧だらけの構文ではなく、問題に合わせて言語を拡張するという考え方にあり、REPL、パッケージ、シンボル、条件、リスタートまで身につけてこそ真価が見えてくる
- コードとデータがどちらもリストで表現される同図像性のおかげで、マクロは評価前のコードをデータのように扱い、新しい制御構造や構文抽象化を作れる
- Common Lispにない
whileもdefmacroで追加できるが、関数でまねると引数が即座に評価され、bodyがコードではなく値になるという違いが生じる
- Lisp開発は、実行中のプロセスにコードを継続的に評価させるREPL駆動開発に近く、関数・マクロ・変数を再定義しながら、プログラムを止めずに進化させられる
- 言語の拡張性とライブシステムが組み合わさると、内部DSLをユーザーに公開する拡張可能なソフトウェアを作ることができ、AutoLISPとEmacsが代表例である
Lispが求める異なるプログラミング感覚
- Lispに初めて触れたプログラマーは、括弧の多い構文、独特のインデント、
formatの第1引数に標準出力を指定するやり方に戸惑いを覚える
- Lispを習得するには、コードを読むだけでなく、パッケージとシンボル、プロジェクト作成、ライブラリの取り込み、REPL、条件とリスタートまで扱う必要がある
- より大きな変化は、アルゴリズムを構成する方法に表れる
- Lispは他の言語では難しいことを可能にし、アルゴリズムの形も変えさせる
- 問題に合わせて先に言語を育て、その言語でプログラムを書く流れへとつながる
- Blub paradoxは、より非力な言語しか使ったことのないプログラマーがLispの強みを見抜きにくい理由を説明する概念である
拡張可能な言語
- Lispは自分自身を拡張でき、Lispの専門家はこれをprogrammable programming languageと呼ぶ
- 中核となる道具は
macroである
- C、Rust、Swiftのマクロのように、重複コードの除去だけにとどまらない
- Lispのマクロは、言語の一部となる新しい構成要素を作れる
- Common Lispには標準の
while演算子がないが、defmacroで自分で追加できる
(defmacro while (condition &body body)
`(loop while ,condition do
(progn ,@body)))
- この
whileマクロは、conditionが真である間、body内の複数の命令を実行する
loopは複雑な反復を書くためのマクロである
prognは複数の式を順番に実行し、最後の式の結果を返す
- 結果として、
(loop while ... do (progn ...))の代わりに(while ...)という形を使えるため、コードが短くなり、Cのwhileに近い構造になる
マクロと関数の決定的な違い
- 同じ動作を関数
fake-whileで作ると失敗する
(defun fake-while (condition body)
(loop while condition do
(funcall body)))
fake-whileの呼び出しでは、引数が先に評価される
conditionは真値tになる
progn内の(print counter)が実行され、(decf counter)が2を返す
bodyはコードブロックではなく、値2になる
- その後、
funcallは関数を期待するが2を受け取るため、The value 2 is not of type FUNCTIONのような型エラーが発生する
- 一方、マクロ
whileはconditionとbodyを即座に評価せず保持する
macroexpand-1で展開結果を確認できる
- コンパイラは展開後の
(LOOP WHILE ... DO (PROGN ...))という形のコードを受け取る
- マクロは関数と違い、引数を事前に評価せず純粋なデータとして扱うため、このような変換が可能になる
リスト、s-expression、同図像性
- Lispプログラムはsymbolic expression、つまりs-expressionの連続で構成される
- s-expressionは2種類に分かれる
- アトム(atom):数値、文字列、シンボルのようなデータ単位
- リスト(list):アトムや別のリストを含むコレクション
- Lispという名前はLISt Processingに由来し、プログラムを構成する命令もリストで表現される
- Lispではリストは主要なデータ構造であると同時に、コードの書き方でもある
- コードとデータがどちらもリストで表現される性質を**同図像性(homoiconicity)**という
- 同じ
(+ 1 2)リストでも、評価するかどうかで動作が変わる
(+ 1 2)はコードとして評価され、3を返す
'(+ 1 2)は評価されず、データリストのまま返される
- Lispはポーランド記法を使い、関数名をリストの先頭要素に置き、その後ろに引数を置く
- コードとデータの境界が薄いため、マクロはソースコードを変換でき、プログラムを書くプログラムを作れる
マクロが作るコード抽象化
- 言語を拡張すると、特定のプログラム領域に対して、より表現力のある新しい構成要素を作れる
- 繰り返し現れるボイラープレートはカスタムマクロで置き換え、コードを減らして時間を節約する
- 新しい制御構造を作り、リソースへのアクセスとフォームの評価を制御できる
- マクロはコードを生成して性能を改善し、複雑でエラーを起こしやすいコードを隠す構文抽象化を提供する
Lispはライブシステム
- Lispは単なるプログラミング言語というより、実行中のライブシステムとして扱われる
- 一般的な言語では、エディタでコードを書いた後にコンパイルして実行し、観察・テスト・デバッグする流れが多い
- LispではまずLispプロセスを起動し、REPLに接続してから、プロジェクトをそのプロセス内にロードする
- REPLはRead-Eval-Print Loopの略で、コードを評価して結果をすぐに見られる対話型環境である
- 現在のプログラムを実行しているLispプロセスを覗き込む窓の役割を果たす
- Lisp開発者は、実行中のプロセス内でコードを継続的に評価し、結果をREPLですぐに確認する
- 個別関数のテスト
- データベースクエリ
- 変数の検査
- デバッグのような作業もREPLで実行できる
- Lispプロセスは停止する必要がなく、数週間生き続けることもできる
- 関数、マクロ、変数は内部メモリである環境(environment)に継続的に定義・再定義され、このような流れをREPL駆動開発と呼ぶ
Lispにおけるホットリロード
- Webフロントエンドでは、ファイル保存やページ更新を通じてJavaScriptコードがホットリロードされる体験が一般的である
- 一般的なホットリロードは、ファイル監視、変更モジュールの再コンパイル、WebSocket経由での変更注入といった別のツールや手法に依存する
- すべてのソフトウェアスタックが同じレベルのホットリロードを提供するわけではない
- 一部のバックエンドフレームワークでは再コンパイルが必要である
- デスクトップとモバイルの開発環境は状況がまちまちである
- 低レベル言語では毎回全体のコンパイルが必要である
- Lispでは、ホットリロードは別ツールではなく、ライブ環境でコードを評価すると自然についてくる
- シンボルを再定義すると、関数、データ、マクロのすべてが新しい定義を使う
- 停止してコンパイルしたり、テストを別途実行したり、特殊なデバッガを使ったりする必要が減る
- Lispプログラミングは、プログラムを組み立てるというより、進化させる方法に近い
拡張可能なソフトウェア
- 言語の拡張性とライブシステムは、Lispで拡張可能なソフトウェアを作りやすくする
- 一般的なデスクトッププログラムは、プラグインやスクリプト言語を通じて拡張される
- 開発者は拡張システムを設計し、保守しなければならない
- ユーザーはプラグインや拡張を作るための特定のAPIを学ぶ必要がある
- Lispでは、マクロで動作をカプセル化し再利用しやすくする中で、自然にDSLを書くことになる
- ソフトウェアを拡張可能にするには、内部で作ったDSLをユーザーに使わせればよい
- CMSサーバーの例では、サーバーサイドのWebページ生成用の
htmlマクロをユーザーに公開できる
- ユーザーはLisp変数、
dolist反復、format文字列フォーマットをそのまま一緒に使える
- 従来のテンプレート言語のように、
{{ user_name }}や{% for %}のような別構文を学ぶ必要がない
- このDSLは、Lispの条件分岐、再帰、高階関数、REPLによる段階的デバッグ能力をそのまま受け継ぐ
- 数学グラフソフトウェアの例では、数式と描画のためのDSLを公開し、ユーザーが曲線や点を少ないコードで描けるようにできる
実例とLispの位置づけ
- AutoCADは反復作業の自動化と複雑な幾何形状の生成にAutoLISPを使用している
- Emacsは独自のLisp方言で大部分が実装され、高い拡張性を備えたテキストエディタである
- PDFリーダー
- Webブラウザ
- メールクライアント
- RSSリーダー
- ターミナルマルチプレクサ
- Gitクライアント
- 音楽プレーヤー
- チャットクライアント
- スプレッドシート
- IRCクライアント
- デスクトップウィンドウマネージャーにまで拡張された事例が挙げられている
- R. M. Stallmanは、Lispを強力でエレガントなプログラミング言語の意味を理解させてくれる言語だと評価している
- Lispがいつか標準言語のように広く使われるという期待は実現せず、今後もそうなる可能性は低そうである
- それでもLispは1960年代から生き残り、Fortranに次いで古い現役プログラミング言語となった
- Lispの価値は、拡張性、対話型環境、REPLといった個別機能のどれか1つではなく、それらの組み合わせから生まれる
1件のコメント
Hacker Newsの意見
プログラミングはライトサイドとダークサイドのあいだの緊張の上に成り立っている
ライトサイドは、プログラマーがミスできないように防ぐ側のことだ。
gotoをなくし、静的型を導入し、バグを表現できないようにする、といった方向性であるダークサイドは、プログラマーに力を与える側だ。マクロ、演算子オーバーロード、自己書き換えコード、複数行正規表現まで好きなだけ許容する
Lispは、プログラマーが何でもできるという点で明らかにダークサイドなのに、ライトサイドのプログラマーたちからも尊敬されているのが興味深い。言語の単純さがプラトン的で、ほとんど堕落しようがないものに見せているのかもしれないし、あるいはLispがあまりにも純粋で、プログラミング宇宙を生んだ神のように両方を包み込んでいるのかもしれない
一緒に働く相手は選べないし、チーム内の実力も選べない。経験の浅い同僚がミスを作ったとき、言語が寛容であるほどその影響は大きくなる
だからコードレビューが必要になるが、そうすると熟練したプログラマーを教師に変えてしまう。他のプログラマーのスキル向上に使う時間がある程度無駄になるのは、プログラマーがいつでも入れ替わるからだ。AIがこの構図を変えるかもしれない
結局のところ肝心なのは小さく熟練したチームを維持することだが、社内政治のせいでそれは簡単ではない。たとえばバス係数への配慮やリリース期間短縮のために開発者を追加する、という話がよく出てくる
高信頼システムはErlangでも書かれており、目を細めて見れば別のLisp方言のようにも見える。Lisp風ErlangであるLFEというS式ベースの版もある
Erlangの信頼性の核心は、例外的なレベルのエラー予防ではなく、エラー回復にある。ライト/ダークという分類自体は気に入っている
静的型がなく、マクロが混沌としうるとしても、宣言的・関数型の思考という基準線がずっと強いので、プログラムをより明確に考え、推論するための出発点が良い
ライト/ダークの区分は単純化しすぎているように見える。プログラミング言語のバランスは非常に複雑で興味深いテーマだ
いろいろな言語を何年も試してみた結果、結局は使う文脈、個人の好み、目標の問題だった。「史上最高のプログラミング言語」などというものはなく、「ライトサイドプログラミングに最適」や「ダークサイドプログラミングに最適」も存在しない
芸術絵画にもライト/ダークサイドのようなものはない。より偉大な絵は確かにあるが、すべての人が他のすべての絵と比べて最も美しいと感じる「最高の絵」はない
https://en.wikipedia.org/wiki/Malbolge
https://en.wikipedia.org/wiki/Clean_(programming_language)
https://en.wikipedia.org/wiki/K_(programming_language)
ここ数か月で素晴らしい新しいエディタやツールが出てきたので、見逃すべきではない
Mineは単一ダウンロードのアプリで、ホットリロードやその場でのデバッグを含め、対話的・増分的開発フローを体験するのに必要なものをすべて提供する。CLとCoalton向けだ
https://coalton-lang.github.io/20260424-mine/
OLIVEはVSCode向けの手作りプラグインで、ICLはターミナルとブラウザ向けの新しいREPLで高度な機能を提供する。おまけとしてJSCLベースのJupyterLiteカーネルもあり、100%ブラウザ上で動作する
すべてここで見られる: https://lispcookbook.github.io/cl-cookbook/editor-support.ht...
mineのソースが見つからない。リンクはある?ウェブサイトに構文ハイライトのバグがあるようだ。本文に含まれているコード片が黒で表示され、マウスで選択すると実際のテキストが見える
デスクトップ版ChromeとiPad Safariで同じバグが見える
回避策は、ブラウザのデバッガでコード要素をひとつ開き、
:not(pre) > codeスタイルにcolor: whiteを追加することだLispの長所を称賛する文章は多い。Lispとそのアイデア、そして言語エコシステムにおける位置づけを冷静に批判する文章も読みたい
この手の文章や、ここで参照されているPGの記事は、結局のところ「分かる人には分かる」に近い。魅力も理解できるし、この文章が明示的・暗黙的にしている主張も理解している
コンピュータプログラミングはこの60年でかなり成熟した。もっと熟慮された検討を含む文章を読みたい
二つ目の欠点は、十分に有能な人たちの時間を何年も食いつぶすのに、その結果がその投資時間を正当化しないことだ。Pythonのようなものでも十分だ。Lispに費やした時間の大半を別のことに使えばよかったと思う
Clojure、ClojureScript、Clojure-Dart、Fennel、Jade、Jank、Jolt、Coaltonは比較的新しい言語で、今も発展中だ。いま思いつくだけでもこれくらいあり、実際にはもっと多い
Lispはある意味ではプログラミング言語ではなく、アイデアだ。今日私たちが使うほぼすべてのプログラミング言語に影響を与えてきたし、今もそうだ。アイデアを「冷静に」批判するのは難しく、群論を批判するのに似ている。ただし、そのアイデアの特定の実装が持つ長所と短所は議論できる
async/awaitや並行性モデルがない。標準は1995年以降更新されていないので、今後も起こらないだろうある人たちには精神的に魅力的に感じられる。本当にニッチな音楽ジャンルにたとえたい。好きな人は少ないが、その人たちは非常に強烈に好きだ。プログラミング界のStockhausenのようなものだ
基本的にはファンダムだ。驚くほど長く続くファンダムではあるが、広く突き抜けたことはなく、今後もそうなる可能性は低い。今では立場はさらに悪くなっている。LLMを武装し、コードそのものにさほど関心がないなら、なぜわざわざLispで書けと指示するのか?
Lispを長くやりすぎて、その機能をきちんとありがたく思えなくなる。そうしているうちに、大衆的で経済的にリスクの低い言語で人々が自滅していく様子を衝撃と恐怖の中で目にすると、突然またLispの価値を感じるようになる
商用の現場では、ドメイン固有言語はアンチパターンだ。誰かがドキュメント不足で自分しか理解できないドメイン固有言語を作って去ってしまう
後から来るプログラマはこの奇妙な言語を解読しようと苦労しなければならない。ほとんどの場合、標準的な言語構造や機能を使うほうがよい。コードは書くより読むほうが多い
実際に反対しているのは、参照透過性の不足である可能性が高い。公平に言えば、Lispを含む多くの言語で問題になる
良い例がCommon Lisp自体の
LOOPマクロだ。誰かの使い方が分かりにくければ、Lispにもっと冗長な非LOOPの基本呼び出しへ展開してもらえばよく、LOOPをまったく理解する必要はない。実際、LOOPフォームを展開後のコードに置き換えることさえできるこれは、ほとんどの他言語でドメイン固有言語コードが実際には解釈されるデータ構造である場合との重要な違いだ
Lispsには本当に強力で独特な点があるが、こうした文章にREPLとホットリロードが毎回含まれるのはやめてほしい
REPLはインタプリタ言語では、さらには一部のコンパイル言語でも、すでにずっと以前から基本条件だった。ホットリロードも独自ではないし、特別広く使われているわけでもない。状態とパッチを扱わなければならないので、推論を容易にするために全体をリセットするのがベストプラクティスとされるのには理由がある
だから他のインタプリタ言語よりはるかに対話的な開発が可能になる
もちろん完璧ではない。しかし、ユーザー体験を反復的に改善するときに行う種類の変更には非常によく機能する
ただし、ホットリロードの高度な部分のかなり多くがCommon Lispですでに扱われていた点は強調する価値がある。REPLも、直接打ち込む場所というより、実行中のイメージとツールが相互作用する非常に強力なインターフェースとして主に使われるという点も同様だ
それでも、これらが存在するという事実だけでは、今日ではそれほど特別ではないという点には同意する
クラス定義がリロードで置き換えられたのに既存インスタンスが残っていたらどうするのか? Common Lisp方言にはこうした問いへの有用な答えがあるが、出会うすべてのLispがそうとは限らない
Bashもホットリロードをサポートしている。次のように
sourceしてその場で更新されるモジュールを開発したことがある$ . /path/to/script.sh有用性は限定的だが、多用するという観点から始めれば、その制約はかなり早く薄れ始める
個人的には、こうした段階はすでに通り過ぎた。Common Lisp、Forth、Haskellを使ったことがあり、『On Lisp』と『Let Over Lambda』も楽しく読んだ
今では、最高のプログラミング言語は Barry Jay の Triage Calculus だと考えている。組合せ論理や非型付きラムダ計算に近い
しかしラムダ計算と違って、Triage Calculus には Lisp の
CARやCDRに似た組み込みの引用と内省があり、型検査を簡単に追加できる。そのため TC では、正しい抽象化さえ作れば、望むどんな構文でも持てる。まさに究極のプログラミング言語だTC は、プログラミング言語の機能や構文と、標準ライブラリやユーザーコードにおける関数/API 定義との間にある区別が、偽の二分法であることを示している。再利用を意図したコードを書くたびに、言語に何かを追加しているのだ。これはすべて TC のある項で表現できる
すべての構文論争は習慣の問題だ。ただし、TC で本当に欲しい構文機能がひとつだけあり、それは
let over lambda抽象化だ。コンビネータを手で並べるのがやや面倒だからだよく書けた記事だ
昔は、いつかみんなが Lisp を使い、優れたツールやライブラリがそろえば、すべてが素晴らしくなると思っていた。実際、引退後の計画も、この流れを早めるために Lisp 向けの高品質ライブラリを作ることだった
AIの台頭がこの夢を終わらせるのだろうか? またしても、より多くの雑多なものを積み増して問題を解決したことになるのだろうか? 超知能 AI は、可能な最高のプログラミング言語でコードを書く代わりに、ただ大量の Python コードをばらまいて、うまくいくまで押し切ることになるのだろうか?
これが重要なのかはわからない。ただ、優雅さのある世界を望んでいただけだ
「Jean-Marie Hullot は INRIA 在籍中に、Macintosh 向けに Lisp で書かれた『SOS Interface』を作り、これが最初の現代的な『interface builder』だった」
https://denninginstitute.com/itcore/userinterface/GUIHistory...
これが NeXT に渡って Objective-C と一緒に使われるのではなく、Mac の主流製品になっていたらどうなっていただろうと思う
LISP は私にはとても合っていた。大学の比較言語の授業では、LISP の課題を全部やり遂げたのは私だけで、LISP を学んだことは TeX を使ううえでも大いに役立った
望んでいるのは、オープンソースのまともなネイティブまたはクロスプラットフォーム GUI ツールキットと、プロジェクトを独立実行可能なコンパイル済みコードとして簡単に配布する方法だ
人間はそのどちらでも良いアイデアを表現できる。言語は道具だと考えるほうがよい。インパクトドライバーが単純な回転ドリルより優れているわけではない。デッキを作るならインパクトドライバーのほうが向いているが、どちらでも仕事はこなせる
この記事は https://web.archive.org/web/20120106121645/http://wiki.alu.o... のことだと思っていた
comp.lang.lisp を追っていた記憶がよみがえる。ちょっとしたドラマのようでもあった。プログラミングの魔術だけでなく、個性の強い人物や出来事も多かった