Skip to content

Instantly share code, notes, and snippets.

@y2q-actionman
Last active January 6, 2024 11:32
Show Gist options
  • Star 85 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save y2q-actionman/49d7587912b2786eb68643afde6ca192 to your computer and use it in GitHub Desktop.
Save y2q-actionman/49d7587912b2786eb68643afde6ca192 to your computer and use it in GitHub Desktop.
A Road to Common Lisp 翻訳

この文章は、 Steve Losh 氏の記事 "A Road to Common Lisp" の翻訳です。

原文はこちらです: http://stevelosh.com/blog/2018/08/a-road-to-common-lisp/


A Road to Common Lisp (Common Lisp への道)

これまで、「最近のCommon Lispをどう学ぶとよいでしょう?」と助言を求めるメールをたくさん受け取ってきました。そこで私は、これまでメールやソーシャルメディアに投稿した全てのアドバイスを書き下すことにしました。これが誰かに有益ならば幸いです。

先に注意: この記事はCommon Lispへの 一つの 道であり、Common Lispへの 唯一の 道ではありません。これは私が辿ってきた道(行き止まりを除く)であり、 大量の 個人的意見が織り込まれています。この言語を学ぶ唯一の方法というつもりはありません。


Context

背景

学習を始める前に、Common Lispはどこから来たのか?、そしてどういう種類の言語なのか?、という感触を持っておくことが重要だと思います。

この言語には、特にあなたが最近の言語から直接やってきたのなら、いくつかとても奇妙に見えそうな点があります。 しかし、ちょっと背景を知っておけば、より意味が分かりやすくなるでしょう。

History

歴史

Common Lisp は、長く、そして深い歴史を持っています。ここでその全てを網羅するつもりはありませんが・・・もし興味があるなら、以下をチェックしてみて下さい(大体、内容の少ない順です):

上のリンク先全部をすぐに読みたくはないのではないでしょうか。そこで、Lispの60年を駆け足でツアーしてみましょう。

Lisp は1950年代後半に始まりました。 MIT の John McCarthy の発明でした。

そこからの20数年間、Lispの複数の版や方言が育ち、繁栄していきました。有名な方言には、Maclisp, BBN Lisp/Interlisp, Franz Lisp, Spice Lisp, Lisp Machine Lisp があり、まだ他にもありました。 重要な点は、 たくさんの 実装があり、全てが育まれ、変化し、異なることを試していたということです。

(Scheme もこの時期に生まれましたが、ここで見ている道とは全然違う道を辿って、分岐していきました。この記事では Scheme はカバーしません。)

1980年代初頭、大量の相互に非互換なLisp方言を持つことは理想的でないだろう、と人々は判断しました。これらの別々に育った異なる言語を持ちよって、全員(少なくとも「全員」の合理的な部分)のニーズを満たす一つの共通言語を作る努力が為されました。1984年に、 Guy Steele の Common Lisp: the Language の初版が出版されました。

ちょっと計算してみると、この本が出版された時点で、Lispには大体25年の実使用、実験、経験、そして歴史があったことが分かります。それでも、この本単体では全員の満足には至らず、1986年に Common Lisp の ANSI 仕様を作るための委員会(X3J13)が組織されました。

委員会が標準化作業を行う間の1990年に、 Common Lisp: the Language の第二版が出版されました。(訳注:邦訳は COMMON LISP 第2版)この本は、より包括的であり、また委員会による作業内容も含まれていました (上でリンクした comp.lang.lisp FAQ を参照のこと)。この時点で、Lisp言語族は30年の経験と歴史がありました。他と比べてみると: Python (多くの人が思う「モダン」で「古い種類」の言語)が最初に リリース されたのは、この翌年のことでした。

1992年に、 X3J13 委員会は、新しい Common Lisp ANSI 標準の最初の草稿を出し、公開レビューにかけました(Pitman の paper を参照して下さい)。この草稿は1994に承認され、最終的に1995年に承認された仕様が出版されました。この辞典で、Lispは大体35年ものでした。 Ruby の初版はこの年の12月に リリース されています。

ここで歴史の講義は終わりです。 Common Lisp の ANSI 仕様に、他の版はありません。1995年に出版された版が唯一で、それが今日まで使われています -- 現在「Common Lisp の実装」を名乗るものが参照する仕様とは、まさにこの仕様なのです。

Consequences

帰結

ここまでは、何を勉強しようとしているのかを知ってもらうため、 Common Lisp の歴史の概要を示してきました。次は、Common Lisp とは、安定し、大きく、実用的で、拡張性があり、そして醜い言語だということを知ってもらいたいと思います。これらの性質を理解すると、この言語を学ぶ上でより理解が進みやすくなるでしょう。それぞれの性質について少しお話しします。

Escaping the Hamster Wheel of Backwards Incompatibility

後方非互換の回し車から逃れる

もしあなたが他の言語から来たのなら、あなたが言語実装やライブラリを「アップデート」して色々壊れる、ということがあったのではないでしょうか。あなたが10年前に書いた Ruby コードを、最新バージョンの Ruby で走らせようとしたら、おそらくアップデートにはある程度の努力が必要でしょう。私の現在の仕事は Scala なのですが、あるライブラリの最後の Github 上の更新が2,3年前だったなら、私はすぐに、私の側でかなりの量の変更をしないと動かないだろうな、と想定します。この 後方非互換の回し車 こそが、私達が毎日扱っているものであり、そして多くのモダンな言語における現実 なのです。モダンな言語には、確かに他の言語よりいいところもあるのですが。

Common Lisp を学ぶと、このようなことは通常ありません。この投稿の次の節で、私は1990年に書かれた本を推薦するつもりです。あなたはその本のコードを、変更することなく、先月にリリースされた Common Lisp 実装で走らせることが出来ます。 後方非互換の回し車 で何年もジョギングしないと、私は書いたコードが20年後も十分動くと期待できることがどれだけ 安心 かを言うことが出来なかったでしょう。

もちろん、これは言語自身についてのみ成り立つことです - もしあなたが何かのライブラリに依存していれが、それをアップデートすればいつでも壊れる可能性があります。しかし私は、コア言語の安定性が他にも伝搬していくと気付きました。そして、大枠において Common Lisp コミュニティでは後方互換性を維持することを良しとしているようです。

しかし正直にいうと: 例外はあります。あなたが言語を学んでライブラリを使い始めると、いくつかのライブラリの作者が、ライブラリにドキュメントをつけたり安定したAPIを保存しようとしていないことに気付くでしょう。もし 回し車に乗らない ことが重要なら、そういう類の人が書いたライブラリに頼らないようにすることを学ぶでしょう。

Practicality Begets Purity

実用性が純粋性を生む

Common Lisp を理解するのに必要なもう一つのことは、大きく、実用的な言語であるということです。 Common Lisp: the Language 第二版(Common Lisp プログラマはよく "CLtL2" と略します)は、序文、参考文献、索引を除いて 971 ページの長さです。あなたは、純粋な Common Lisp だけで、他のものの助けを得ずとも、すごい量の物事をこなすことが出来ます。

Common Lisp でアプリケーションをプログラムするときは、普通、少数の安定したライブラリだけに依存するようにされます。一方でライブラリを書くときには、できる限りコア言語を利用して依存性を最小化しようとします。 私の場合、アプリケーションには大体10以下、そしてライブラリには2,3未満(出来れば0)の依存しかないように心掛けています。しかし、私はおそらく他の人よりちょっと保守的です。私は 本当に 回し車が好きじゃないんです。

もう一つ特筆すべきことは、 Common Lisp は長い間安定しており、結果、多くのプログラミング言語よりも古くて安定した ライブラリが沢山ある ということです。例えば: Bordeaux Threads (Common Lisp のスレッドライブラリのデファクトスタンダード)は、2004年に提案され、しばらくして(遅くとも2006年ですが、もう少し早いかも。多くがデッドリンクになっていて、確認が難しい)リリースされました。つまり14年前です。そうです、スレッドはライブラリで扱います。しかし、私はライブラリであるからといって、これから10,20年後に壊れるかもしれない、なんて心配はしていません。

私からの助言は: Common Lisp を学び、ライブラリを見てまわる時、「このプロジェクトの最終更新は6年前?もう放棄されてて壊れてるんだろうな」という脳内の声は抑えましょう。 Common Lisp は安定しており、つまりライブラリはただ 完了 しているのであり、 放棄 されているわけではないのです。そのようなライブラリを見逃さないようにしましょう。

Extensibility

拡張性

Common Lisp の実用性の一部は、その拡張性から来ています。 新機能を足した新しい版の仕様をよこせ、などと誰も騒がないのは、 Common Lisp の拡張性により、ユーザは新機能を単なる普通の ライブラリとして言語に足すことができるからです。しかもコア言語を変える必要はありません。マクロは、「Lisp の拡張性」が語られるときに気になったことがあるかもしれませんが、もちろんこの拡張性の一部です。マクロにより、ユーザは、他の言語ならコア言語の機能でないといけないような機能を持ったライブラリを書くことができます。

Common Lisp には文字列補間が含まれていません。必要ですか? 問題ありません、単に ライブラリを使いましょう。 Scala 2.10Python 3.6 のように待つことはありません。

ボイラープレートを書かずに非決定的なプログラミングを試したい? ライブラリを掴みましょう。

パターンマッチ構文は、本当に美しくて、読みやすいコードを書かせてくれます。 Common Lisp には含まれません、が、もちろんライブラリがあります

Haskell や Scala のような代数的データ型で楽しみたい? これがそのライブラリです。

これらのライブラリは、シームレスに使えるようにするためにマクロを使っています。もちろん、これら全てをマクロなしで 行う ことはできます。しかし、それには評価の管理のためにボイラープレートを足さねばなりません。例えば以下は:

(match foo
  '(list x y z) (lambda (x y z) (+ x y z))
  '(vector x y) (lambda (x y) (- x y)))

次に示すようには流暢に打てないでしょう:

(match foo
  ((list x y z) (+ x y z))
  ((vector x y) (- x y)))

パターンマッチングが入ったCommon Lisp 標準の新しい版をよこせと憤慨する人がいないのは、それをライブラリとして書くことができ、組み込みであった場合と比べて9割以上のものは手に入るからです。この言語では、最初からあったかのように言語を拡張するという手法がとれるので、拡張に十分な力が与えられてます。

他の言語ではコア機能にあるようなものがライブラリを供給されているという点は、前節で述べた依存性の最小化の話と合わせてみると、おかしく思えるかもしれません。ある意味それは正しいです。しかし私は、コア言語に対しては安定したライブラリを書き、そしてアプリケーションにおいては特定の問題に必要な機能だけを追加する少数のライブラリにのみ依存する、ということが出来る楽しい中間地点にいると思っています。

Power

マクロは Lisp の拡張性の一要素です。なぜなら、マクロにより、任意のコードを別の任意のコードへと変換することが出来るからです。確かにC言語などにもマクロはあります。しかし、 Common Lisp のマクロは、それが言語の一部であるという点で異なっています。

C言語では一番上にマクロの層があり、これはプリプロセッサマクロ言語で書かれます。このマクロの層と言語の層は互いに別れています。マクロの層により、1つ追加の抽象力 の階層が与えられているのです(誤解しないでください、これもおそらく有用です。)

Common Lisp では、 Common Lisp 自身で マクロを書きます。このマクロを関数を書くのに使い、そうして書いた関数をマクロを書くのに使えます。 2つの分かれた層ではありません。抽象力の 循環 があります。

一方、 Common Lisp においてはマクロだけが実用性と拡張性の源というわけではありません。多くの人が気付いていないのは、 Common Lisp はマクロによって高水準言語たるのですが、その一方で多くの低水準機構も言語の一部であるということです。 C, Rust, Forth ほど低水準ではありませんが、 ANSI 仕様に含まれる機能には驚くかもしれません。

ある関数がどんなアセンブリコードにコンパイルされたか見たい? DISASSEMBLE しましょう!

ガベージコレクションを避けるためにスタックに確保したい? X3J13 が考えてくれています

グラフィックボードのために unboxed float の配列が必要? 標準でできます

GOTO は有害・・でなく有用と思う? 了解、みんな大人だもんね。 幸運を、自分の足を打ち抜かないよう気をつけて。

ゲームボーイのエミュレータに符号無し8ビット算術が必要で、しかもそれを機械語で1,2命令にコンパイルして欲しい? 可能です

実際には、全ての Common Lisp 実装がこれらの最適化の全てを行うというわけではありません。しかし、 Common Lisp の設計者はこれらをサポートするのに必要な機能を言語に含めるよう洞察していました。標準で定められた Common Lisp で書いておけば、それはどこでも走るでしょうし、ここで述べたような最適化をサポートする実装なら最適化の機会を十分に生かしてくれるでしょう。

このような、マクロによる非常に高水準なプログラミングのサポートと、十分な量の低水準な最適化の組み合わせがあるので、仕様が20年ものであっても、今日でも何かを作るのに堅実な基盤になっています。30年の経験と歴史により、設計者は何十年も生き延びるとても実用的な言語を作りあげることが出来たのです。

Ugliness

醜さ

もう一つ知っておくべき重要なことは、 Common Lisp は実用的である一方で、既存のユーザや方言の調整をしなければならず、つまり醜い部分がたくさんあるという点です。もし Common Lisp: the Language 第二版の紙版を買ったら、索引から "kludge"(訳注: 「その場しのぎの浅慮な方法」の意)を探してみてください。すると:

CLtL2の索引の写真。"kludge" は1ページから971ページとある。

Common Lisp はプログラミング言語のデザインにおいては美しい水晶ではありません。大きなペグボードの壁に道具がぶら下げられた薄汚ない工房、床にはほこりが薄く層となり、オフィスの棚の引き出しが何個もあって垂直に開いていて、20年以上誰も座ってない側には RPLACD という名の変なノコギリがあるのです。

この歴史的お荷物は、 Common Lisp に未来を与えるための代償です。これによって、古い方言を使っていた人は、手頃な作業で Common Lisp に乗り換えることが出来ました。もし設計者が完璧で美しいものを作ろうとしていたら、既存の実装やコードを移植するのがとても困難になり、結果としてこの言語は、採用や受容されることもなく、無視されてしまっていたでしょう。

A Road to Learning Common Lisp

Common Lisp 習得への道

ここまでの話で、この言語に怯えていないでしょうか? それでは、2018年における習得法を話しましょう。

インターネットで Common Lisp のチュートリアルや手引きを探しても、期待するほど多くは見つからないでしょう。この理由は、多くの Common Lisp を参照する資料がインターネットの幼児期かその前に作られたからです。 Common Lisp についての本は 多く 出されており、善し悪しがあります。私が最高と思うものを推薦するつもりですが、ためらわずに他のものを探して下さいね。

Get a Lisp

Lisp を得る

Common Lisp を始めるには、 Common Lisp の実装をインストールしなければなりません。 Common Lisp は ANSI 仕様で、その実装が複数あり、選択の余地があります。選択肢は何個かありますが、ここでは単純なものを用意しました:

  • MacOS を使っていて、 App Store からダウンロードできる single GUI アプリが好みなら、ClozureCL を選びましょう。(ClozureCL は、よく “CCL” と略されます。)
  • それ以外の場合は、 SBCL を選びましょう。

ここの Clozure は Z です。 Clojure は名前がとても似ていますが、全然違うものです。

CLISP という名前も聞いたことがあるかもしれません。あなたが必要なものに聞こえるかもしれませんが・・違っています。 CLISP は単に一つの実装なのですが、8年間リリースされておらず(レポジトリ上ではまだ開発されているのに!)、 CCL や SBCL ほど使われていません。そのため、インストールについて等の質問に、答えを見つけるのが大変だと思います。

Roswell という名前も聞いたことがあるかもしれません。 Roswell は使わないで下さい。あなたにとって(まだ(もしくはずっと))それは必要ではないです。

単に SBCL か CCL をインストールしましょう。他の選択肢を探すのは、あなたの中で潤滑がよくなってきてからにしましょう。

Pick an Editor

エディタを選ぶ

Common Lisp を学ぶ前に Emacs を学ばないといけない、なんて言う人がいるかもしれません。その人は間違っています。あなたが好きなテキストエディタなら何でも、言語の学習を始められます。

環境設定せずとも、 CCL には MacOS 用のエディタがバンドルされています。始めるにはこれで十分です。

Emacs, Vim, Sublime Text, Atom, その他なんでも大丈夫です。括弧のバランスを取ってくれて、コメントと文字列がハイライトされて、Lispが自動インデントされれば、始めるのに必要なものは揃っています。エディタの yak shaving で悩むのは、言語に慣れてからにしましょう。

(訳注: yak shaving (ヤクの毛を刈る) とは、「一見無関係に見えるけど、真の問題を解くのに必要な問題を解くのに必要な(これが何段階も続く)問題を解くのに必要な活動」という意味らしい。)

Hello, Lisp

こんにちは、 Lisp

ここまでの設定が上手くいっているか確かめるため、 hello.lisp ファイルを以下の内容で作りましょう:

(defun hello ()
  (write-line "What is your name?")
  (let ((name (read-line)))
    (format t "Hello, ~A.~%" name)))

まだ意味については気にしないで下さい。単に、全部が適切に動いているかの確認です。

SBCL か CCL の REPL (Read/Eval/Print Loop) を開いて、 (load "hello.lisp") と入力して上記のファイルを読み込み、関数を呼んでみて動くか確かめましょう。 SBCL なら以下のようになるはずです:

$ sbcl
* (load "hello.lisp")

T
* (hello)
What is your name?
Steve
Hello, Steve.
NIL
*

CCL を MacOS アプリからではなく command line (この command line プログラム、64-bit システムだと ccl64 という苛立つ名前になってます )から使っているなら、以下のようになります:

$ ccl64
Clozure Common Lisp Version ...

? (load "hello.lisp")
#P"/home/sjl/Desktop/hello.lisp"
? (hello)
What is your name?
Steve
Hello, Steve.
NIL
?

もし矢印キーやバックスペースが REPL で動かなかったら、 rlwrap で直せます。 rlwrap sbcl で、 REPL の悲惨さを減らせます。 rlwrap 自体、あなたの道具箱に入れておきたい便利ツールです。

A Gentle Introduction

やさしい導入

私が見つけた中で Common Lisp の入門に最適の本は、 Common Lisp: A Gentle Introduction to Symbolic Computation です。この本は、本当にやさしくすることに努めています。あなたにプログラムの経験があっても、この本は言語へ導入を楽にしてくれるので、この本から始めるのをおすすめします。

1990年版はサイトから無料で利用可能です。2013年に再販され、1990年版の細かい間違いが修正されています。もし余裕があるなら、2013年版を買うのを勧めますが、1990年版も十分立派です。

本を読みながら、 練習問題を全部やりましょう 。時間がかかるでしょうが、これは Common Lisp に慣れる上で克服すべき障害を乗り越えるためです。例えば:

  • どうやってこの変な関数名を覚えるの?
  • どうして文字列がめったに使われないのか?
  • この神の気まぐれのような引用符はいつ必要なのか?

この本の進行が遅すぎると思ったら、ちょっと飛ばし読み しましょう。飛ばし読みはプログラマの訓練として、とても重要な技能です。私は、本やドキュメントを書くときには、その著者は説明しすぎてしすぎる位がいいと思います - 説明しすぎたところで、専門家の読者は快適に読み飛ばせますが、説明が軽すぎると新しいユーザは混乱して立ち止まってしまうでしょう。初心者に悲惨と混乱の時間を与えるのと、専門家がスクロールする数フリックを節約するのとは、まずいトレードオフです。

Freenode IRC の #clschool チャネルに参加しましょう。困ったときに質問できます。ほとんどの人はフレンドリーで親切です。しかし、少なくともひとり、時々不快にさせてくる奴がいることを先に警告しておきます。 #clnoobs というチャネルもありますが、最近の Freenode spam の波のせいで、ほとんど放棄されています。だれも spam と戦う管理をしてくれなかったのです。

IRCが好みでないなら、 我々がいる Discord server もあります。 #common-lisp に参加してくれれば、いつでも助けになります。

Getting Practical

実用的に

上述の本を終えたら、 Practical Common Lisp に挑みましょう。紙版も買えますが、本全体がサイトから無料で使用できます。(訳注: 邦訳は 実践Common Lisp。紙版しかないはず。)

エディタ/プログラミング環境構築の部分は飛ばせます。この本の勧める環境 (Lisp in a Box) は放棄されていて、動きません。あなたが慣れているプログラミング環境を使いましょう。

不幸なことに、この本は練習問題を含んでいません。もし 本当に 最大に活用したいのなら、読んだ全てのコードを写経することもできますが、前の本の練習問題を全部こなしているのなら、単に座って慎重に読むだけでおそらく大丈夫です。一日に読むのは1,2章くらいにしましょう。全ての情報を脳が消化するには時間がかかります。

全てを理解していることを確かめながら、本を読みましょう。分からないことがあったら、 IRC や Discord で怖気づかずに質問して下さい (私に email してもかまいませんよ)。

Common Lisp 言語仕様自体を情報を探すのにも慣れましょう。これは、 Common Lisp の究極のマニュアルです。所々でとても高密度ですが、ゆっくりと慎重に読めば多くの疑問の答えが分かります。何か探すには、索引も使えますし、単純に Google で "clhs なんとか" と検索してもいいです(CLHS とは “Common Lisp HyperSpec” のことです。言語仕様にハイパーリンクを付けた HTML 版です)。 MacOS で Dash app を使っているなら、 Common Lisp 仕様も利用可能になっています。

(言語を学ぶなんて仕様を読むだけだ、と言う人がいるかもしれません。それは馬鹿げています -- フランス語を勉強するのに辞書を読むことから始めるようなものです。辞書は有用な道具ですが、必要な唯一のものではありません。)

Make Something

何か作ろう

二冊の本を身に付け、仕様を使う練習をしたら、そろそろ手を自由にして何かを作る時です。大きなものや特別なものである必要はありません。目標は、次ページの解答を見たりするなどせずに、何か Lisp コード を書くことです。

アイデアが必要なら:

何を作るかは問題ではありません。とにかく自身で なにか 作りましょう。

Lisp as a System

システムとしてのLisp

ここまで来たら、そろそろ Common Lisp の技能を一段階上げる頃です。これまでは、言語を経験することが大事なので、どんなテキストエディタを使ってもいいと言ってきました。しかし、深淵に飛び込む時です。

多くの言語で、開発プロセスは以下のようなものです:

  1. プロジェクト内のコードをエディタで編集。
  2. プロジェクトをコンパイル (これを飛ばす言語もある)。
  3. プロジェクトを動かす (もしくはテスト)。
  4. 出力を調査 (コンソール、ブラウザなどで)。
  5. 1 に戻る。

この流れは、多くの Common Lisp ユーザが言語と対話する方法ではありません。 Common Lisp では、開発サイクルは以下のようになります:

  1. Lispプロセスを起動。
  2. プロジェクトを読み込む。
  3. コードをエディタで編集。
  4. 動作中のプロセスに、編集したコードだけをコンパイルさせる。
  5. プロセス中の変更したコードと対話。 REPL や HTTP request などを通して行う。
  6. 出力を調査 (コンソール、ブラウザなどで)。
  7. 3 に戻る。

Lispの作業方法を採用すると、プロジェクト全体を再コンパイルしてリロードすることはめったになくなります。通常、関数(もしくはマクロ、パラメータなど)を書き、その関数だけをコンパイルし、 REPL でそれをつついて御手、次の関数に移ります。これには、従来の 全部コンパイルして実行 アプローチに対して利点があります。

一つめ: コードの一部分をコンパイルするのは速いです。私は、私のプロジェクトの中で大きい関数をいくつかコンパイルして時間を測ってみましたが、 50-80 マイクロ秒でした。コンパイラを待つ必要がなくなり、集中/思考のプロセスがさまよう時間がなくなります。

もうひとつの利点は、 コンパイル(と実行)の結果が返ってくると、受け取ったエラーや警告のほとんどはあなたがコンパイルした数行のコードに関するものだということです。10行の関数をコンパイルそ、実行し、ゼロ除算エラーが出たら、あなたはすぐにさっきコンパイルした10行に焦点を当て、何を変更したかを考えることができます。

Lispプロセスは常に動作しているので、関数をコンパイルしたら、すぐに REPL で使用できます。その関数の上に色々と作り込む前に、単独でどう動くかを確認するため、任意のデータを投げこんで結果を観察することが出来ます。このような、関数を作り、コンパイルして、期待した通り動くか確かめるためにつついて、次に移動、という繰り返しがいつも起こっています。

対照的に、 Scala や Python で仕事する場合、私は、一つの関数を書いたらプロジェクトをコンパイルや実行する、ということはほとんど行いません。コンパイラを回したりユニットテストを走らせたりするには少なくとも1,2秒(もしくは Scala の場合で 数分 、残念)かかるので、思考が一定間隔空いてしまうのを避けるため、複数の関数をまとめて書き上げ、その後で動かす機会を見てプロジェクトやテストを走らせます。

しかし、もしエラーが見つかると、もっとずっと広い面を確認しないといけません。大量の新しいコード追加したのですから! そのため、4分前に書いたかもしれない問題を追跡しなければなりません。一方 Lisp なら、数秒前に書いたコードを見るだけで済むでしょう。

私は、少しでも苦痛をやわらげるため、ScalaでIntelliJを使い始めました。これは、再コンパイルを on the fly に行うので、コンパイル時間に対して役に立ちます。しかし、残りの問題は解決しません。私はIntelliJでScala関数を書くことができ、すぐにコンパイルされるのですが、Common Lispのように即座に 対話 することはできません。

この Common Lisp スタイルで作業すると、私はあなたが本当にそれを愛するようになると思います。他の言語で書くことが、コードを DMV に送り、やっと1週間後に結果が戻ってきて、見てみるとあなたが埋めた数百の書式は赤インクまみれだった、というように感じられます。Common Lispを書くことは、生物や呼吸する組織とやりとりすること、もしくは 熱心な助手に物事を教えること のように感じることができます。

(訳注: DMV とは、アメリカの Department of Motor Vehicles (車両管理局)のことと思われる。判が悪いらしい。)

このような、 単なるプログラミング 言語 ではなく、生きた呼吸するプログラミング システム である、という Lisp の哲学は、短いフィードバックループやインタラクティブなREPLを超えて現れます。

例: 想像して下さい。ビデオゲームを作っているのですが、ダメージ計算のどこかにバグがあり、時々ゼロ除算が発生してしまいます。さて、あなたは特定のクエストのコードに取り組んでいるとします。ゲームを開始し、クエストの開始のセーブファイルをロードし、手順を開始します。突然、クエストの最後のモンスターを殺す途中で、あなたはダメージバグを踏みました! 伝統的な言語では、次の2つのうちの1つが起こるでしょう:

  1. ゲームはクラッシュする。あなたは、スタックトレースと、もしかしたらコアダンプを得る。
  2. ゲームのメインループを try ブロックで囲んでおいて、スタックトレースをログに出してエラーを無視し、ゲームを続行する。

ケース1はかなりまずいです。その時点でどのように見えるかというスナップショット(スタックトレースとコアダンプ)からバグを追跡しようとしています。そして、修正することができたとしても、あなたが元々仕事していたクエストのコードのテストに戻るには、すべてのプレイをやり直す必要があります。

ケース2もまずいですが、ちょっと違っています。常にエラーを無視していると、ゲームはおかしな状態になる可能性があります。問題をデバッグするのに必要な重要なコンテキストを無くしているかもしれません。コアダンプも保存していない限り(ただし、例外が発生するたびにコアダンプを保存する人はいません)。

Common Lispでは、パニックさせたり、エラーを無視したりすることも選択できますが、より良い作業方法があります。 Common Lispでエラーが通知された場合、スタックは巻き戻されません。 Lispプロセスはその時点で実行を一時停止し、エディタにスタックトレースを示すウィンドウを開きます。あなたの戦士の剣はモンスターの上に浮いていて、あなたを待っています。この時点で、REPLで実行中のプロセスと通信し、何が起きているのかを知ることができます。スタック内の変数を調べられますし、必要ならば任意のコードを実行したりすることさえできます。

問題が分かったら(「ああ、わかった、同一フレーム中にシールドの呪文が切れると calculate-armor-percentage 関数は 0 を返すんだ」)、コードを修正し、問題のある関数を再コンパイルし、 その関数(と他のものなんでも!)の実行を同じコールスタック中で再開できます 。あなたの戦士の剣はモンスターに当たり、あなたが前に行ってたことに戻れます。

スタックトレースだけからバグを追跡する必要はありません。さながらそれは、壁にある血液のしみから、何が起こったかを調べようとする探偵のようなものです。あなたは犯罪が 起こる度に 調査し、介入して被害者を救うことができます。何か問題が起きた場合にのみアクティブになるようなブレークポイントを各行に張るデバッガでコードを実行できるかのようです!

たぶんあなたはビデオゲームを作っていないでしょう。しかし、このプロセスはあらゆる種類の文脈で役立ちます。もしかしたら、どこかのAPIと話すWebアプリケーションを作成中で、 2つのAPI呼びだし、例えば 「foo ウィジェットを作成する」と「ウィジェットのリスト barfoo を追加する」の間に失敗するリクエスト をデバッグしているかもしれません。リクエストを中止し、スタックトレースをログに書き、結果おかしな状態のもの(foobar のリストに入ることなく作成されてしまった)が残される・・ということにする代わり、問題を修正してリクエストを正しく終了させることができます。

もちろん、これはいつもうまく動くわけではありません。複数の副作用を起こす大きな関数を使っていて、それがクラッシュする場合、その機能の実行を再開すると、副作用が再び発生してします。しかし、関数をうまく分割すると(1つの関数には1つの機能!)、この状況はほとんど起きなくなります。それでも起こったとしたら、 他の言語を使っているのと同じ デフォルト の状況に戻っているということです!

このような対話的スタイルの開発のサポートは、格好いいエディタプラグインに由来するものだけではありません -- 言語の骨に焼き付けられています。 例えば: 標準では update-instance-for-redefined-class という名前のメソッドが規定されており、クラスが再定義されたときにオブジェクトに何が起きるかをカスタマイズできます! これはいつも使うようなものではありませんが、Sketch(Java の Processing ライブラリの Common Lisp 版に相当)では、クラスを再定義するときに実行中の sketch を自動的に更新するために 使用してします 。実行中のコードを安全かつ一貫性をもって動的に更新するのに、Common Lisp では闇魔術を使う必要はありません。想定内で、普通の仕事だからです。

さあ、どうやったら、この素晴らしい対話的な体験を実際に できる でしょう? 悪いニュースです、あなたはエディタの yak shaving をしないといけません。ここでは本当に2つの選択肢しかありません:

  1. Emacs と SLIMESly.
  2. Vim (or Neovim) と VlimeSlimv.

私はこれが事実でなければと願っているのですが、今日の実際に現実的な選択肢はこれだけなのです。((高価な)商用のLispの編集環境は除く。)

あなたが、私のように既に Vim が取れないくらい指に深く焼きついている場合、Vim に Vlime を組み合わせるのをお勧めします。 Emacsでの体験の80%を得ることができます。

そうでなければ、Emacsに行きましょう。 Portacle を調べたくなるかもしれません。これは Emacs と SLIME など諸々を一緒にバンドルしたものです。もしくは、 Emacs と SLIME や Sly を自分で設定することもできます。私は Emacs 側ではあまり多くアドバイスできません。私はあまり経験がないのです。あなたはここで少し調査をする必要があります。

どちらを選んでも、エディタと環境を設定するのに時間を費やします。これは多くの面倒なメタ作業となりますが、あなたがLispで作業を続けていくうちに、格好よく清算されるでしょう。

原注:誰かが Common Lisp 用の LSP 言語サーバを作ることに興味があれば、それはコミュニティにとって非常に有用な貢献になると思います。 LSPサーバを持っていると、多くのエディタで、よりすぐれたプログラミング経験を得ることができ、新しい人々をかなり助けられるでしょう。

私は、 多くの言語側のことは Swank 上で橋渡しできると思っています。主に LSP インターフェースを実装するという問題になります。面白いと思われる場合は、私に教えてください - 私は喜んで助けます。私は IntelliJ をバックエンドに使用する Scala LSP 言語サーバーを作っており、ソーセージがどう作るかについて少しアイデアがあります。私は、Common Lisp 用のLSPサーバ全体を一人でやる時間とモチベーションがないんです。

Learning Paradigms

パラダイムを学ぶ

この時点で、あなたは Common Lispの基本についてはかなり良く扱えるようになっていて、より強力な開発環境の1つを設定しました。あなたの次の目標は、慣用的なCommon Lispを書く方法を学び、あなたの新しい環境を使った練習をすることです。

これらの両方のための完璧な本が、 Paradigms of Artificial Intelligence Programmingだと思います。この本はよく PAIP と略されます。この本は最近、PDF形式で無料で入手できるようになりました。また、必要なら中古を購入することもできます。(訳注: 邦訳は 実用Common Lisp。紙版しかないはず。)

この本は1992年に書かれており、つまり、機械学習のような、ニュースでよく聞く類の盛り上がっている AI 分野の本ではありません -- 古き良き AI の本です。この種のAIに特に関心がない人にとっても、この本はCommon Lispコードの書き方の素晴らしい例です。

私がこの本で本当に愛していることの1つは、ほとんど全ての関数が docstring を持っていることです。たいていの他のプログラミングの本を見ると、スペース上の理由とか、そして周囲のテキストが十分な文書であると感じるためか、 docstring は省略されています。しかし、有益な docstring を書くことは、それ自体が芸術です。それらを省略した書籍は、「良いコードは docstring を省略するものだ」という読者を養成することになってしまい、これは悪い習慣です。

この本には たくさんの 練習問題が含まれており、難易度や含む内容で簡単に分類されています:

  • S for “seconds”.
  • M for “minutes”.
  • H for “hours”.
  • D for “days”.

これは、もっと多くの本に盗んで欲しいとても良いアイデアです。 SM の練習問題をすべて行い、少数の H も自力で試しましょう。 D が特に興味深いと思えば、恐れずに時間を費やしましょう -- 本当に問題を掘り下げることは、Lispの旅のにおいて必要なものです。

Switch Things Up

切り替える

そろそろ、 Common Lispとあなたのプログラミング環境に慣れてきているでしょう。もう一度、自分の快適ゾーンから自分自身を押し出す時間です。最初は SBCL か CCL を選択しました。今度は、最初に選択しなかったものをインストールし、これまでに書いたすべてのコードがその中で実行されていることを確認してください。

これは、同じ場所で走っているように見えるかもしれませんが、複数の実装でコードが動くことを確認することで、正直さが保てます。 これにより、今後10年,20年の間に変化するかもしれない実装依存の機能に依存しない、移植可能なコードを書くことが強制されます。さらに、元の実装より好みに実装を見つけることになるかもしれません - CCL の超高速コンパイル時間があなたを笑顔にするでしょう。 SBCL の強力な型推論は多くのバグを見つけてくれます。

これまでに書いたコードをすべて見直し、すべてが新しい実装で動くことを確認してください。この機会に、リファクタリングや書き直しをしたいと思うかもしれません - あなたが最初に始めてからずいぶん学んできたので、あなたの最初期の Common Lisp コードはかなり荒削りに見えるでしょう。

Recipes for Success

成功へのレシピ

すべての Lisp プログラマ に勧める最後の技術書は、 Common Lisp Recipesです。 CLR と省略されることもあります。これまで推奨してきた他の書籍とは異なり、これは比較的最近のものです: 2015年に出版されました。無料ではありませんが、コストに見合うものだと思います。

この本は、非常に頻繁に使用される Common Lisp ライブラリの作者によって書かれています。この本は、トピックごとの雑多な寄せ集めで構成されています(この点が、この本に取り組む前にまともな量の Lisp 経験値が必要だと思う理由です)。しかし、これは非常によく書かれた寄せ集めであり、 他の本では見付からない多くのことを教えてくれます。

Final Patterns

最後のパターン

ここまで来たのなら、あなたは Common Lisp に多額の投資をしていることになりますね。私はあなたに1つ、厳密な技術書ではないけど本当に楽しめると思う本を紹介します: Richard Gabriel の Patterns of Software です。著者のサイトでPDF形式で入手できます。必要なら、中古の印刷物をオンラインで見つけることもできます。

これは、あなたがすでに読み聞きしたであろう "Gang of Four" の "Design Patterns" (訳注:たぶん オブジェクト指向における再利用のためのデザインパターンのこと)のような本ではありません。さまざまな緩く関連した話題のエッセイです。今年私が読んだ中での最高の本です。私はあなたの何かを台無しにしたいとは思わないので、あなたの時間を使う価値があると思うものだけを言っています。

Where to Go From Here

ここからどこへ向かうか

あなたが前の節で言及した全ての本と活動を完遂したなら: おめでとうございます、あなたは素晴らしいスタートを切りました! ここまで来れば、あなたはコア言語を適切に扱うことができ、興味に応じてさまざまな方向で探索することができるでしょう。

Macros

マクロ

マクロの秘訣について学びたいなら、 On LispLet Over Lambdaを(この順番で)読んで作業したくなるでしょう。

(訳注: On Lisp の邦訳は On Lisp で公開されており、紙版もある。 Let Over Lambda の邦訳は LET OVER LAMBDA Edition 1.0。)

言っておきますが、どっちの本も(特に後者は)あまり鵜呑みにしないようにしましょう。 多くの Common Lisp ユーザは、これらの本における議論やスタイルの全てに賛同してはいません。しかしそれでも、批判的な精神を持って読めば、まだ沢山の価値を提供していると思います。

Object-Oriented Programming with CLOS

CLOSでのオブジェクト指向プログラミング

Common Lisp には、CLOSによる非常に洗練されたオブジェクト指向プログラミングのサポートがあります。もしあなたが私のように、 Javaタコ部屋労働 のせいで OOP に嫌な思い出があるというなら、 CLOS にあなたの心を変える公正な機会を与えてあげるようお願いします。

Object-Oriented Programming in COMMON LISP: A Programmer’s Guide to CLOS から始めましょう。これは、すばらしく書かれ、短く、そして的を射た本で、 CLOS をどのように使用するかについてよい概観を与えてくれます。

もし本当に没頭したいなら、 The Art of the Metaobject Protocol(よく AMOP と略されます)を試しましょう。この本を読破するには、おそらく何度か試行する必要があるでしょう。頭がいっぱいになるまで読んで、2ヶ月ほど別のことをして、また戻ってきましょう。必要なだけ、この過程を繰り返しましょう。

Low-Level Programming

低水準プログラミング

低水準プログラミングという言葉には、色々と異なる意味があります。ここでは、ありそうな一つについでだけ言及しましょう。

古いコンピュータのエミュレータを書くことに興味があるでしょうか。 私はかつて、 Common Lisp でCHIP-8のエミュレータを作る、という投稿をしていました。 cl-6502は NES (や他の多くのマシン) で使われていたプロセッサのエミュレータです。 本当にすごい文芸的プログラミング版もあり、通し読みにも素晴らしいです。

Web Development

Web開発

残念ながら、私は Common Lisp での web 開発について、あまり提案できることはありません。私は、過去5年以上、意識して web 開発を避けてきました。なぜなら、その領域では 後方非互換の回し車遠心分離機 やそれ以上のものになっているように思えるからです。

Freenode には #lispweb チャネルがあり、 Lisp Discord には #webdev チャネルがあります。質問があれば、そこで聞くことから始めてみましょう。これらのチャネルは、他の Lisp チャネルほどは人がいないので、すぐに答えが返ってくるとは期待しないでください。

Game Development

ゲーム開発

Common Lisp には、小さいながらも熱心な、ゲームを作るのが好きな人コミュニティがあります。 Freenode には #lispgames チャネルがあり、 Lisp Discord には #gamedev チャネルがあるので、興味があるなら参加してみるとよいでしょう。

Land of Lisp(訳注:邦訳は Land of Lisp)は読んでいて楽しい本です。この本のコーディングスタイルは、少し・・ “エキセントリック” です。このため、私は、 Lisp の最初の本としてはお勧めしません。(例: 整数の除算に truncatefloor でなく ash を使っています。)しかし、言語については知っていて、何か単純なゲームを作ってみたいのなら、この本で仕事することは楽しめるのではないかと思います。

Lisp で1週間でゲームを作るという口実がほしければ、Lisp Game Jam に参加できます。通常は毎年1〜2回開催されています。次の日時を調べるには検索(または #lispgames で質問)してください。

Lisp には Unity のようなフル機能のエンジンはありませんが、 3D ゲームエンジンの作成に現在取り組んでいる人々がいます。人々が最近何を使っているかを見て回りましょう。残念ながら、3Dゲームエンジンは、画像の描画や音声の再生のためOSとのインターフェイスを一般的に必要とし、これは純粋な Common Lisp だけでは書けません。これはつまり、 OSの変更に追従するために 後方非互換の回し車 で走る必要があるということです (例: Apple は OpenGL を非推奨にしました)。

もし、伝統的な ASCII/タイルベースのゲームに興味があるなら、私は個人的に Common Lisp で ncursesbearlibterminal で仕事をしたことがあります。 telnet 上で遊べるゲームを作るのは、本当に楽しいです! そのようなものに興味があり、もっと知りたければ、連絡を取り合いましょう。

Window Management

ウィンドウマネージャ

Linux を使っていて、デスクトップ環境を変えたいのであれば、 Common Lisp で書かれた X Window マネージャである StumpWM があります。私は最近 Linux に戻ってきて、たった 2ヶ月 ほど使っただけですが、 Common Lisp で自分の作業環境をカスタマイズというのはできるのは本当に楽しいことです。

StumpWM には小さいがフレンドリーなコミュニティがあります - あなたが non-trivial な オープンソースのCommon Lisp プロジェクトで貢献先を探しているなら、 StumpWM は素晴らしい選択です。

Unit Testing

ユニットテスト

あなたが最近の言語、特にテスト駆動開発論者が多くいるような言語から来たのなら、 Common Lisp ではユニットテストが強調されないことに驚くかもしれません。私が思うに、ユニットテストが強調されないのには理由があります。いくつかの言語においては、関数を実際に 走らせて みる一番簡単な手段がまさにユニットテストなのですが、 Lisp の対話的な開発スタイルは、もっと簡単な代替手段を与えてくれます: 単純に REPL で 関数を走らせ ればよいのです!

コミュニティではユニットテストに重点が置かれていませんが、ユニットテストの フレームワーク は、 Common Lisp プログラマの数と同じくらいあります! これはおそらく、ユニットテストのフレームワークはちょっとのマクロで簡単に作れるからです。 私は1amが好きですが、選択肢は他にも たくさん あります。

どのようなものを選んだ場合でも、良き市民たるために、ユニットテスト用に ASDF システムを分割して下さい。こうしておけば、他の人があなたのライブラリを使う際に、 また別の テストフレームワークを読み込む必要がなくなります。

More Implementations

実装をもっと

私はあなたに SBCL か CCL を使ってもらっていましたが、その理由は、これらが今日において一番普及した無料の Common Lisp 実装だからです。しかし、これらだけが開発継続中のものではありません。あなたが試してみたいと思いそうなものは、他にもたくさんあります:

  • ABCL は JVM 上で動きます。
  • ECL は C言語に埋め込むことができ、 Common Lisp を C のコードに変換することもできます。
  • CLASP はまだ開発途中ですが、 C++ との相互運用が簡単にできるようデザインされた実装です。
  • LispworksAllegro CL は商用の実装であり、多くの追加機能やサポートがあります。しかし無料ではないです。

(私は CLISP を省略しました。理由は、初心者を混乱させるような名前を選んだことに、ちょっと怒っているからです。 えーと、この投稿は 個人の意見™ だと警告済みでしたよね。)

私は自身のプロジェクトには SBCL をよく使いますが、全ての私のライブラリのユニットテストが SBCL, CCL, ABCL, そして ECL で走ることを確認しています。これにより、私は正直に、移植性のあるコードを書いているという自信を合理的に持つことが出来ています。

Modern Common Lisp

モダンな Common Lisp

Common Lisp が古くて安定しているからといって、停滞しているという意味にはなりません。この言語は、ものを作りあげるための沢山の力を与えてくれます。まとめをする前に、 ここまであなたが学んできた古い本では語られていない、 Common Lisp 界隈の最近の開発動向を見渡してみたいと思います。また、よく初心者がつまづく点を明確にしておきたいとも思います。

Structure

構造化

Common Lisp プロジェクトの各パーツについての用語は、初心者にとって混乱の種になりがちです。この理由は、古いせいで、今日では多くの人が微妙に違う意味で使っている言葉をたくさん使っているためです(例えば "パッケージ")。各用語が Common Lisp ではどういう意味なのが頭に入ると、物事が分かりやすくなります。

(原注:私は、飛行機を待っている間、Lobste.rs にこの節の簡易版をコメントとして投稿していました。この記事のこの節は、そのコメントの拡張版です。)

Packages

パッケージ

IRC や Discord では、よくこんな質問があります: 「パッケージからクラスをエクスポートするにはどうすればいいですか?」 この手の質問は、パッケージについてのとてもよくある誤解の兆候です。パッケージとは 実際には 何なのでしょうか。

Common Lisp のパッケージとは、シンボルのコンテナです。 これだけです。パッケージは、関連した名前(シンボル)を一つにまとめる手段であり、おかげでしょうもない mylibrary-... のような接頭辞を付けてまわる必要はありません。 Emacs Lisp や C での名前衝突の回避のようなことは必要ありません。

あなたがパッケージからエクスポートするのは、クラスではなく、 シンボル です。あなたがパッケージにインポートするのは、関数ではなく、その関数が紐付いた シンボル です。これは衒学的に聞こえますが、パッケージシステムを使うにあたって頭をすっきりさせるのには重要な点です。もしシンボルとは本当は 何なのか がすっきりしないなら、シンボルについて別に書いた記事があります。助けになるでしょう。

もう一つの初心者がよくつまづく点は、パッケージとファイルの関係ですが・・ Common Lisp においては、関係があるどころか、全く関係 ありません

Python, Java, Clojure といった多くの言語では、ファイルのパッケージとハードドライブ上の位置とが一緒になっています。例えば: Python で import foo.bar.baz と書くと、 Python は foo/bar/ ディレクトリの baz.py ファイルを探しにいくでしょう(実際にはこれより少し複雑ですが、ここの例として問題にならないでしょう)。

Common Lisp では、こういうことにはなりません。 Common Lisp において、ファイルとパッケージとは完全に無関係です 。 たくさんのファイルを同じパッケージで動かすことが出来ますし、一つのファイルで複数のパッケージに切り替えることもできます。さらには、実行時にパッケージを作成したり変更したりすることも出来ます。

これにより、必要なだけ柔軟に作業できます。例えば: 私の手続き型アートライブラリ Flax では、ほとんどのパッケージはそれぞれ一つのファイルに使われていて、モダンな言語で行うのと同様になっています。しかし、 flax.drawing パッケージは、描画プロトコルだけでなく、プロトコルの実装 (PNG, SVG, 他)をいくつか含んでいます。そこで、私はコードを複数のファイルに分割し、個々のファイルで一つずつフォーマットの描画方法を扱うようにしました(フォーマットを扱うファイルの他、プロトコル自体の実装を扱うファイルがもう一つあります。)

それぞれの描画プロトコル実装に対して別々のパッケージを作って、互いにインポート/エクスポートして作り上げることも可能でしょう。しかし、私はそういう追加のボイラープレートが価値に見合うと思いません。 Common Lisp は十分に柔軟で、どちらの選択肢もとることができます。

さて、ファイルとパッケージとが関係ないというならば、次の質問はこれでしょう: Common Lisp は、コードを読み込む時に、ディスク上のどこを 探す かをどうやって知るのでしょうか?

Systems

システム

Common Lisp におけるシステムとは、いくつかのものの集合です:

  • コード。
  • コードをどうやって読み込むか、という記述。
  • このシステムが依存している他のシステムのリスト。依存しているシステムは、このシステムより先に読み込まないといけません。
  • メタデータ。著者、ライセンス、バージョン、ホームページなど。

Common Lisp 言語自体には、システムの知識がありません。 CLtL2 の section 11.9 を見ると、各作者がコードを読み込むためのカスタムコードをそれぞれ書き下す、といことが想像されていたようです。しかし Common Lisp には、ほとんど何でも抽象化できる力があるのですから、結局のところ Common Lisp コードを読み込む過程は抽象化されました。

ASDF は、ほとんどのモダンな実装にバンドルされている Common Lisp ライブラリで、システムの定義と読み込みを扱うものです。 ASDF という名前は “Another System Definition Facility” に由来しています。そういうライブラリがいくつもあったということは、想像に難くないでしょう。 ASDF は現在みんなが使っているものの一つです。

ASDF は、システム定義というプロセスを、以下のように標準化しています:

  • ある foo プロジェクト向けのシステム定義は、 foo.asd という名前のファイルに置く。
  • このファイルの中で、 (defsystem ...) という式で各システム定義をする。

この後は、"プロジェクト" とは何なのかについて少しお話しします。 注意: ファイルの拡張子は asd であり、 asdf ではありません。これは少し紛らわしいですが、おそらくは拡張子に3文字の制限がある環境で動かすために選ばれたのでしょう。

ASDF manual は、 defsystem 文法とセマンティクスについての決定的なリソースですが、始めたばかりの人が読むにはちょっと大変です。もうひとつの始め方は、中小規模のオープンソースプロジェクトの .asd ファイルを読んで、物事をどう扱っているか見てみることです。

Common Lisp において、システムとパッケージとは直交しています。あるシステム(小さいパッケージ等)は、一つだけパッケージを定義しているでしょう。他のシステムでは、複数のパッケージを定義しているでしょう。新しいパッケージを定義しないシステムはめったにありませんが、既存のパッケージを使ったり、そこに追加したりするようなシステムも考えられます。

例えば:

  • 私の有向グラフライブラリ cl-digraph は、 cl-digraph という名前のシステムを含みます。
  • このシステムにはコードの読み込み方法が記述してありまずが、それは cl-digraph.asd ファイル中にあります。
  • 読み込むよう指定されているファイルの一つが package.lisp であり、 digraph というパッケージを作ります。

ASDF はシステム定義の一面を標準化して固めていますが、それでも十分な柔軟性を与えてくれます。いろいろなプロジェクトを読んでいると、作者によってシステムの構成法が違うという場面に出喰わすでしょう -- この点は、最初はちょっと圧倒されるかもしれませんが、この意味する所は、あなたが システムごとに最適な方法で システムを構築できるということです。経験が伴うと、この点は本当によいものです。

多くの人々のシステムの定義方法が、これの一つの例になっています。現実世界で見られる手法には、2つのよくあるものがあります:

  • 単一の package.lisp ファイルに、プロジェクト中の全てのパッケージ定義を含ませ、他のファイルより先に読み込みます。これは私が普段好む方法です。
  • 各ファイルにおいて、ファイルの先頭で自身のパッケージを定義します。 Clojure や他のモダンな言語の方法によく似ています。システム定義における注意点は、個々のパッケージが使われる前に定義されるよう、正しい順序でファイルを読み込むことです。

復習: システムとは、コードと、それの読み込み方法の記述、依存性のリスト、そしてメタデータです。さあ、もう一段階上に行きましょう。構造化のためにあなたが知る必要がある最後の層です。

Projects

プロジェクト

Common Lisp において、プロジェクトという言葉は、私の知る限り公式に定義されてはいません。しかし、ライブラリ、フレームワーク、アプリケーション等を意味する単語として一般的に使われています。

プロジェクトでは、普通は最小で一つはシステムを定義します。システムとはコードの読み込み方法を記述するものですから、プロジェクトがシステムを定義しないのならば、どうやってコードの読み込み方法を知るのでしょう? 私が書いた文字列の折り返しライブラリである Bobbin は、二つのシステムを定義するプロジェクトです:

  • bobbin システムは、実際のデータ構造とAPIを含みます。依存関係はありません。
  • bobbin/test システムはユニットテストです。 依存しているのは、 bobbin システム(テストしたいコードそのものなので)と 1am システム(ユニットテストのフレームワーク)です。私がこれを別システムにしたのは、テストを走らせるつもりがなく、メインのコードだけを読み込むユーザが、ユニットテストのフレームワークも読み込まなくていいようにするためです。

これらシステムの両方がbobbin.asd ファイル で定義されています。 ASDF は名前にスラッシュが入ったシステムを特別に扱い、スラッシュの前までのテキストの名前の asd ファイルから探してくれます。

ここまで、 Common Lisp にはシステムという概念がないということを見てきました。システムという概念は ASDF に由来するものなのです。同様に、 ASDF にはインターネットとか、どこかダウンロードできる場所にいくとかいう概念がありません。ASDF は、あなたが読み込みしたいシステムを何とかして取得して、あなたのハードドライブに保存していると仮定しているのです。もしかしたら・・どこかの住所に小切手を送って、なんとかフロッピーディスクでコードのコピーを受け取ってみたら、そのディスクは私の裏表紙がなくなった古いLispの本くらいの量があった、なんて状況かもしれませんが。

Quicklisp は ASDF の上で動く別のライブラリで、「必要に応じてインターネットからプロジェクトを自動的にダウンロード」機能を提供します。この機能は、モダンな世界ではあると期待されているものです。つまり、 (ql:quickload :bobbin) とすると、 Quicklisp に対して Bobbin (と依存してるもの) を必要ならダウンロードしてくれ、とお願いすることになります。そして、結果が ASDF に渡され、ASDF が bobbin システムの実際の読み込みを行います。

ASDF と違って、 Quicklisp は Common Lisp 界隈において比較的新しいものであり (大体8年前のものです)、私が知っているモダンな Lisp 実装にはバンドルされていません。このため、個別にインストールする必要があります。

Recap

おさらい

ここで、おさらいです。 あなたが Common Lisp で出会うであろう、プロジェクト構造化のいろいろな層は、以下のようなものです。学習中にいつでも参照できるよう、ポスト・イットに書きとめておきましょう。

  • ファイル は、あなたのハードドライブにあるファイルです。
  • パッケージ は、シンボルのコンテナです。ファイルとは直交しています。
  • システム は、コードと、コードの読み込み方法と、依存性のリストと、メタデータの集合です。パッケージとは直交しています。
  • プロジェクト は、 "もの" の高水準集合です・・ “もの” とは コードやドキュメント、画像アセットなどなどです。これらは、(ほとんど)システムと直交しています。(流れがわかってきましたか?)
  • Common Lisp 自身には、 ファイルとパッケージがあります。
  • ASDF により、システムが追加されます。
  • Quicklisp により、インターネットが追加されます。

Common Libraries

共通ライブラリ

Common Lisp には、新興言語のような大きいコミュニティはありませんが、長期間コミュニティが存在しているので、たくさんのライブラリがあります。コア言語の安定性のおかげで、Common Lisp で可搬に書かれた多くのライブラリは、 10年15年前のものであっても、今日でもそのまま動きます。

この最後の説では、あなたが言語を学んだら使うことになりそうな、多くの有名ライブラリの概要を示します。使う必要はありませんが、使えるものを知っておくことは助けになります。

Alexandria

Alexandria は Common Lisp で最も有名なライブラリの一つです(この名前は、アレクサンドリア図書館のもじりです)。あらゆる種類の有用なユーティリティ関数の詰め合わせです。 read-file-into-byte-vectormap-permutations などがあります。

Common Lisp 界隈には、 たくさんの ユーティリティライブラリがあります - あなた個人のユーティリティライブラリを作っていくのは、通過儀礼のひとつです - しかし、 Alexandria が最も有名です。何か依存関係を持つプロジェクトの依存関係を辿っていくと、ほとんどどれも Alexandria に辿り着くことになるでしょう。

Bordeaux Threads

Bordeaux Threads は先に触れました。スレッドは Common Lisp 標準の一部ではありません。しかし、ほとんどの実装は、その実装固有のインターフェイスを持っています。 Bordeaux Threads はこれらの実装固有のインターフェイスの全てをラップした API を提供するので、可搬に動くスレッドを使うコードを書くことができます。

もし、Java でいう所の new Thread(() -> foo()).start() みたいなものを探しているなら、これがそれです。

CFFI

CFFI は、外部関数インターフェイスのライブラリです。これを使うと、 C ライブラリ(例: foo.dylib とか foo.so)を読み込み、その関数を呼び出すことができます。 Common Lisp 標準に含まれない、実装依存のインターフェイスをラップすることで動きます。

残念ながら、 Python の FFI ライブラリも同じ名前です。ドキュメントを検索するときには、正しい方を見ているか確認しましょう。

CL-PPCRE

CL-PPCRE は、Perl 互換正規表現の実装です。 Common Lisp で正規表現を使おうとしているなら、これを使いましょう。

Drakma

Drakma は HTTP クライアントです。 HTTP リクエストをする必要があるなら、これを使いましょう。他にも HTTP クライアントはありますが、 Drakma は広く使われており、あなたが必要なほとんどの場合できちんと動きます。

Iterate

Iterateloop マクロの代替です。これは、 loop と同様の働きをしますが、より Lisp 的な構文を持っており、上手く定義された API で拡張して新しい反復構造を定義することもできます。私はこれがとても好きなのですが、注意: 一度 Iterate に慣れると、単純 loop には戻れなくなってしまうでしょう。

local-time

local-time は、 Common Lisp で時刻や日付を扱うためのライブラリです。標準には、時刻について組み込みの基本的サポートがありますが、時刻(タイムゾーンも含む)にたくさん計算を行いあいなら、おそらくこちらを使ったほうがいいでしょう。もし Joda Time のようなものを探しているのなら、得られるものの中でこれが一番近いです。

lparallel

lparallel は、 Bordeaux Threads 上に作られたライブラリで、典型的な平行処理をずっと簡単にしてくれます。 GNU Parallel の追加機能つき Lisp 版と思ってください(追加機能には、 task や channel があります)。

例えば: 巨大な配列があって、 (map 'vector #'work some-vector) で map するような場合、(lparallel:pmap 'vector #'work some-vector) と変えることで、小さく分割して複数スレッドで走らせることができます。

Named Readtables

Named readtables は、リードテーブルに名前空間を足すライブラリです。

言語標準の辛い箇所の一つは、リーダマクロが実行時にグローバルなリードテーブルに対して追加したり削除されたりする点です。そのため、同じリーダマクロを定義する複数のシステムを読み込むと、ひどいことになり得ます。 Named readtable は、この過程に必要な健全性を追加します。リーダマクロを使っているなら、絶対にこれを使いたくなるでしょう。

Roswell

Roswell は、いくつかのものの詰め合わせです。これは、複数の異なる Common Lisp 実装のインストールと実行を扱う C プログラムです(NVMrvmの類のものです)。また、小さなシェルスクリプトを書いたり、バイナリへのコンパイルをする場合に統一された方法で出来るようにしてくれます。

私は1年ちょっと Roswell を使いましたが、結局やめました。私はこれは価値に見合わないと思います。二つ理由があります:

一つめ: 可搬なコードを書いたのなら、ある実装の特定のバージョンで動くかどうかを悩む必要は普通ありません。 Common Lisp は安定しているのですから。私は普段、使っている実装の最新バージョンは、パッケージマネージャでインストールするか、もしくはソースからコンパイルしています。

二つめ: しばらく使った後、私は Roswell がアップグレードに対していつもとても脆弱と気づきました。何かが壊れたときには、まともなエラーメッセージではなく、 JVMサイズのスタックトレースを吐き出します。

私にとっては、悪い点が良い点を上回っています。単純に興味がある実装の最新バージョンを使って、可搬なコードを書くことをお勧めします。バイナリにコンパイルする機能については、あなたの実装の組込みのサポートを使うか、 UIOP にあるラッパーを使う、もしくは Deplay のような個別ライブラリを使うのをお勧めします。

もちろん、あなたの道程は変わり得ます。もし、特定の実装の特定のバージョンを素早く連続して動かす必要が 本当に あるのなら、 Roswell を調べてみましょう。

SERIES

SERIES は、 Common Lisp にほとんど含まれかけていました(Appendix A of CLtL2にあります)。しかし、そうはなりませんでした。これは、関数型のコードを書くライブラリで、伝統的な mapfilterreduce の見た目でコードを書き、それを効率的なループにコンパイルすることが出来ます。

もし Clojure の transducers のようなものを Common Lisp で探しているなら、これがまさにそれです。

st-json

Common Lisp の JSON サポートは、ひどく混沌としています。 不合理な数の JSON ライブラリがあり、私は本当にどれも 好き になれません。

私にとって、 JSON ライブラリにおいて必要であろう最も重要な品質とは、曖昧さがなく、型が1対1に対応することです。例えば: あるライブラリは JSON 配列を Lisp list に、 JSON の true/falset/nil にデシリアライズします。しかしこれの意味するところは、[]false はどちらも nil にデシリアライズされるということであり、つまり信頼性のある巡回が不可能なのです!

私は st-jsonを使用し、さらにそれをちょっとした人間工学的グルーコードでラップすることに落ち着きました。これは最速の方法ではありませんが、私の用途には対応しています。他の選択肢がたくさんるので、もしあなたの用途が私と違っているなら、それぞれを調べてみてください。

usocket

usocket は、ソケット ネットワーキングのためのライブラリです。ソケットとネットワーキングは、 Common Lisp 標準には含まれていませんが、多くの実装がそれらを扱うための独自インターフェイスを持っています。 usocket は、実装依存のインターフェイスをラップし、可搬なネットワーキングコードを書くための API を提供します。

もし、 Lisp にポートを待ち受けさせて、クライアントから来たバイトストリームを読みとりたい、もしくはあるポートに接続して生のバイト列を送りたいのであれば、このライブラリを使いましょう。

Good Luck!

幸運を!

この駆け足のツアーが役に立てば幸いです。 Common Lisp は、古く、深い言語です。一ヶ月で学べるというものではありませんが、時間を費やす気があるなら、慎重に研究する価値があります。

質問があれば、気軽にメールなり IRC や Discord で呼び出すなりしてください。

幸運を!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment