八発白中

はてなブログに引越しました。

Day 9: Quickdocs

これは fukamachi products advent calendar 2016 の9日目の記事です。

今日はQuickdocsについて話します。珍しく今回はライブラリの話ではありません。

ドキュメント生成の歴史

2日目のClackの回でも少し触れましたが、Clackは単に完成物としてのプロダクトだけに留まらず、自動テストやドキュメントの完備にも力を入れていました。

Clackではアクセス可能なシンボルすべてとパッケージすべてにドキュメント文字列 *1 を書きました。そしてリリース後にドキュメント文字列から閲覧可能なHTMLを生成するツール「clack-doc」を作りました。

clack-docは名前の通りClack専用のものとして作られ、のちにCavemanにも対応しました。適応ライブラリが限定的なのは、Clackが1ファイル1パッケージを前提にしたものであることや、ドット区切りの独特な階層パッケージ名なども意味のあるものとして解釈し、そういう分割単位でHTMLを表示したからです。

ドキュメントの不備

それから1年ほど経った頃、これでは不十分だなと感じ始めました。

少し脱線になりますが、ちょうど@hyotang666さんが問題意識として挙げていたので紹介します。

今日のツイートなのでまだ解決されていない根深い問題なのでしょう。これに対していくらか賛同の声もあるようです。

ただ、僕はこれに対する「自分さえわかればいい」という姿勢であるという考えは違うと思います。

ドキュメントは欲しい、けれどそういった手間がかけられない、というのが実情だと思いますし、事実僕のライブラリの周りはそうです。

ドキュメントの整備が比較的優先度が低いのはおかしい、とかCommon Lisperは初心者のことを考えていないのではないか、という声も当然あるとは思います。

まあ、言ってしまえばそうです。Common Lispのコミュニティのリソースは他の人気言語に比べて圧倒的に少ないです。その少ないコミュニティリソースで今切望とされているのは、実は初心者ではなく開発者です。コントリビューターです。そして開発者ならば、ドキュメントがなくともコードを読んでパッチを送ってくれます。ドキュメントの整備よりも、プロダクトの開発と改善にリソースが全力投資されている、というのが実情ではないでしょうか。

最近は「Common Lispはライブラリが足りない」という声を聞かなくなってきました。ライブラリが足りないという問題に比べるとドキュメントが足りないという問題は少しマシなのかなと思っています。

けれどそういった中でも、せめて自動生成のドキュメントくらいはあってもよかろう。そういう意図で作られたのが「Quickdocs.org」です。

Quickdocsはドキュメントの生成ツールに留まらず、Quicklispに登録されているすべてのライブラリをパースしてドキュメントを自動生成し、それを同時にホスティングするというものです。

自動生成なのでライブラリの設計思想などはREADMEなどを見てもらわなければいけませんが、自動生成には更新のコストが低いという利点もあります。そのためQuickdocsは常に最新の状態を保つことができました。

Quickdocsの意義

当時にもドキュメント生成ツールやHTMLで閲覧可能なものはありました。

たとえばdocbrowser。REPLでロードして関数一つ呼び出すだけで、REPLにあるシンボルの一覧を表示するHTMLをlocalhostで見ることができます。

他にはCommon Lisp Documentation Search Engine。これはQuicklispに登録されているライブラリのシンボル名を検索できるWebサービスです。

これらとQuickdocsが異なる点は、Quickdocsは常に更新されていてどこからでもWebで同じ見た目で見られることです。

同じ見た目で見られるというのは重要です。ドキュメントを見る時にライブラリごとにドキュメントページの使い方を覚えるほど不毛なことはありません。同じ見た目と使い方であることは安心感も与えます。

さらにQuickdocsはAPIリファレンスの生成にも哲学があります。「コードは雄弁である」ということ。

以前までのCommon Lispのドキュメントはシンボルを集めてアルファベット順に羅列しただけです。これは検索するにはいいかもしれませんが、ざっとライブラリの全体像を把握したいというときには何が大事なのかわかりません。クラスとアクセサがまったく別のところに表示されているなども起こりうることです。

Quickdocsではファイル毎に、出現した順番でドキュメントを並べています。

f:id:nitro_idiot:20161209080154p:plain

優れたプログラマが書いたと仮定するならば、ファイルに出現する順番にも意図があるはずです。上のほうにあればより重要だったり、隣り合う関数定義には関連性があったり。そういった意図を尊重するため、どう表示するか迷ったら「極力コードを尊重する」という方針で開発者の意図がユーザに伝わるようなドキュメントを生成する工夫をしています。

さらなる機能

Quickdocsはドキュメントを閲覧するだけでなく、必要なライブラリを探せるというWebサイトでもありました。そういった用途で重要なのは、このライブラリは使っても大丈夫か、信頼性はあるかという情報です。

Quickdocsには独自の機能として、依存するライブラリと依存されているライブラリを一覧できる機能があります。

たとえばClackの場合は36の依存ライブラリがあり、15のライブラリに依存されているようです。

f:id:nitro_idiot:20161209010404p:plain

依存されているライブラリが多ければそれなりに市民権を得ていると言えます。

また、Quicklisp badgesというREADMEに埋め込む用のバッジもあります。

Quicklispに登録されている場合は登録されているdistの最新バージョンが Quicklisp のように表示されます。登録されていない場合はQuicklispのように表示されます。

これはライブラリがきちんとアップデートされているか、開発が続いているかということを確認するのに役立ちます。

この点はまだできる余地はあるとは思いますが、簡単にできるものだけ手を付けているといった状況です。

おわりに

QuickdocsはGitHubで公開されています。

明日のアドベントカレンダーは10日目のSxQLについてです。お楽しみに。

*1:Pythonをやっている方は馴染み深いかもしれませんが、関数の中に文字列を書くと関数シンボルと結びつき、documentation関数でアクセスすることができます。このドキュメント文字列は変数やクラスやスロットはもちろん、パッケージなどにも書くことができます。

Day 8: Shelly

これは fukamachi products advent calendar 2016 の8日目の記事です。

今日はShellyについて話します。

注意

今まで取り上げたツールとShellyの異なる点は、Shellyは古いプロジェクトであり現在非推奨である点です。これを読んで技術的な興味以上の実用的な意図で使うことは推奨しません。

シェルとS式

Common Lispのアプリケーションはターミナルからスクリプトとして実行することはあまり考えられていません。普段はREPLでS式を入力して実行するだけで十分です。けれどもSSHで入ったサーバでアプリケーションを実行したい場合には面倒なことになります。

sbcl --eval '(ql:quickload :clack)' --eval '(clack:clackup #P"app.lisp" :server :fcgi :port 3000)'

いわゆるワンライナーですが、単にライブラリをロードして関数一つ呼ぶだけにしては大袈裟です。

きっかけは些細なことから

Clackの影響元となったPerlPlackにはplackupというWebアプリケーション実行用のコマンドがあります。

plackup app.psgi
plackup --server HTTP::Server::Simple --port 9090 --host 127.0.0.1 app.psgi

このコマンドを見ていて気づくことがありました。--serverというのはclackup:serverに相当します。このコマンドを受け取ってキーワード引数に変換して関数呼び出しすれば汎用的なコマンドラインインターフェースが作れるのではないか。

その突発的なアイデアで作られたのがShellyです。

Shellyを使うと先ほどのclackupのコマンドがこのように実行できます。

shly -Lclack clackup app.lisp --server :fcgi 3000

シェルから渡された引数はすべて文字列になるのですが、値によって適切な型に変換してから関数に渡します。同名のファイルがあるときはpathnameに、コロンで始まるときはキーワードに、数値のときは数値に暗黙に変換されます。

初期はPerl5で書かれていました。Common Lispでない理由は処理系の依存をなくすためにブートストラップが必要なためです。

CIMやRoswellへの影響

コマンドラインCommon Lispアプリケーションを実行したいという要求は思ったよりもあったようですが、Shellyのインターフェイスが最善かというと疑問もありました。僕自身Shellyは実験的なプロジェクトだと捉えており、多く使ってもらうことで改善点が見つかればと考えていました。

それから2年が経とうという頃、κeenさんがCIM (Common Lisp Implementation Manager)を作りました。処理系の管理ツールなのですがコマンドラインインターフェースを備えており、それがShellyに影響をされているそうです。

さらに1年後、snmstsさんがCIMに影響を受けてRoswellを作りました。CIMと同様に処理系の管理ツールですが、こちらは移植性のためにCで書かれています。コマンドラインインターフェイスの抽象度はShellyやCIMに比べて低いですが、RoswellスクリプトによるCommon Lispスクリプト化が簡単になりました。

そしてその数ヶ月後、takagiさんがLakeを作りました *1。これはGNU makeのCommon Lisp版と言えるものであり、Shellyのshlyfile *2に相当する仕組みです。直接的なShellyの影響はありませんが、Shellyが使われなくなりRoswellスクリプトが一般的になったという環境の変化から自然と生まれたものでしょう。

そういう過程を経てShellyはもうすべての機能を他のプロジェクトに譲りました。Shellyのプロジェクトは廃れましたが、コマンドでの実行環境としての実験は後のプロジェクトに少しばかり影響を与えたという点で成功だったと言えるかもしれません。

おわりに

ShellyはGitHubで公開されていますが現在は非推奨です。

明日のアドベントカレンダーは9日目のQuickdocsについてです。お楽しみに。

*1:元はRudolph-Millerリポジトリだったがtakagiさんがジャックして勝手に開発した

*2:shlyfile.lispに関数を定義することで自由にプロジェクトローカルのコマンドを作れる機能

Day 7: CL-DBI

これは fukamachi products advent calendar 2016 の7日目の記事です。

今日はCL-DBIについて話します。

フレームワークバトルは次のステージへ

Cavemanに一定数のファンがついた頃、Star数がRESTASに並びました。そのとき僕はうれしくて、@snmstsさんに言いました。「佐野さん!CavemanがようやくRESTASを超えたよ!」

すると佐野さんが言ったことはCommon Lispフレームワークの歴史的な一幕です。

「もうCommon Lispフレームワーク同士で争っていても仕方がない。他の言語と比較して優位性が出せなければ」

他の言語のフレームワークと比較してCavemanはどういった利点があるのか、ということを言えなければCommon Lispの導入など到底できないということです。

なんでPerlを書いてるの?

はてなで働いている頃の話。確か東京から@m2ymさんが遊びに来たときだったと思います。はてなエンジニアたちで御池通間之町を下がった「みまでり」という店に行きました。奥の階段を昇って2階の手前のテーブルに座ったことまで覚えています。

酒も2杯目くらいでほどほどに料理も食べたとき id:antipop さんがおもむろに「にとろ君、なんでCommon Lispで(仕事のコードを)書かないの?」といつもの調子で絡んできました。

いつもの調子、というのは、僕が個人的にCommon Lispが好きであり趣味のコードはCommon Lispで書いているにも関わらず仕事で平気な顔でPerlなんて書いているのか、全部Common Lispにしちゃえよ、という無責任な煽りです。

ひょっとするとCommon Lispで書くことや技術力に自信がないからじゃないの?という含みもあったかもしれません。

もちろん僕はCommon Lispが好きですが、すべてのプログラムをCommon Lispで書くべきだと思っているわけではありません。ライブラリや結合予定のアプリケーションとの都合もあります。チームで開発する場合は自分以外のメンバーのスキルセットも考慮する必要があるでしょう。やりたいことと天秤にかけて実現可能性が高いほうを選びます。Common Lispで書いてもプロジェクトが失敗すれば意味ないですからね。

当時、はてなの規模のWebサービスCommon Lispで書くのはかなりチャレンジングだったでしょう。そして実現可能性はPerlに比べて極めて低かったです。僕がそのときantipopさんに言ったマジレスの一つはDBIの欠如でした。

DBI

DBI (Database independent interface) とは使うRDBMSに依らずに統一したインターフェイスを提供するライブラリです。最近の言語では標準ライブラリとして付属していて当然ですが、Common Lispには標準ライブラリがありません。

Common Lispに当時あったのはCLSQLでした。ただこれが恐ろしく使いづらい。なんで使いづらいのかうまく説明できないのだけど、おそらくインターフェイスをリッチにしすぎているために抽象レイヤーが厚くなっていてわかりづらいのかなと思われます。いわゆるORMのような機能も入っていました。また、コードとしてもとても古くFFIとしてUFFIという今では非推奨となっているライブラリを使っています。開発も止まっておりメンテナンスも十分ではないようです。

これを改善していくよりも新しく作ったほうがよかろう。そう思ってより薄い抽象レイヤーで、DBの統一インターフェイスの提供のみを行うライブラリを作りました。それが「CL-DBI」です。

2つの統一インターフェイスの違い

統一インターフェイスという意味でCL-DBIClackと似ているかもしれません。

Clackは各Webサーバに対して統一的なインターフェイスを提供することで、同じアプリケーションをコードを書き換えることなくまったく別のWebサーバーで動かすことを可能にしました。一方でCL-DBIは使うRDBMSを自由に切り替えることができます。

しかし、作ってみてわかったことですが、CL-DBIの抽象化は不十分でした。

RDBMSは汎用的なSQLという言語を使ってデータにアクセスできるという意味で共通ですが、そのSQLRDBMSごとに微妙に違い、同じSQLではエラーとなる場合が多々あります。たとえばMySQLにはAUTO_INCREMENTがありますが、PostgreSQLの場合はSERIAL型を使い、実装もsequenceとしてテーブル定義とは別扱いになります。SQLiteはAUTOINCREMENTです。さらにインデックスはありません。

そのため、CL-DBIはClackのようにまったくコードを修正しなくても動く、というほど理想的ではないのです。

また、需要の面でも違いがありました。

Webサーバと違ってRDBMSの場合は複数種類を使うというユーザーが少ないです。MySQLファンはPostgreSQLは使わないですし、PostgreSQLファンはMySQLを使いません。そういう意味で、PostgreSQLだけ使うつもりの人はCL-DBIではなくcl-postgresやPostmodernで十分なのです。それにPostmodernは可搬性を捨てた代わりにPostgreSQL独自の機能が使えるというメリットを掲げています。

CL-DBIの有用性

それではCL-DBIは失敗だったのか。他の言語で当たり前になっている統一インターフェイスは間違ったデザインなのか。

結果から言えばそうでもありませんでした。

CL-DBIはとても薄い抽象レイヤーのため、Webアプリケーションで使おうと思ったらもう少し扱いやすくしたものを使いたいという願望が生まれます。そこでCL-DBIを前提としたツールが作られました。それがIntegralCranedataflyMitoなどです。

CL-DBIを使ったこれらの周辺ツールは、一つのRDBMSには依存しません。*1 そのためMySQLユーザもPostgreSQLユーザも共通して使うことができます。

共通のツールを使えるということはいくつもの利点があります。知識がユーザ間で共有しやすく、コミュニティを形成しやすくなります。さらに、同じライブラリの開発者も増えることが見込めます。バグ報告も増え、クオリティの向上にも有利です。

CL-DBIは僕のプロダクトの中では結構初期からあるライブラリですが、年を減るごとに重要性は増しています。

おわりに

CL-DBIGitHubで公開されており、現在Starは74です。

明日のアドベントカレンダーは8日目のShellyについてです。お楽しみに。

*1:IntegralはSQLの抽象化ができていないためPostgreSQLに対応していません。これは後継のMitoにより解決されています。

Day 6: circular-streams

これは fukamachi products advent calendar 2016 の6日目の記事です。

今日はcircular-streamsについて話します。

circular-streamsとは

これまでのプロダクトとは違ってcircular-streamsは裏側で使われているライブラリなので知らない人も多いと思います。

Common Lispのストリームはreadを始めて一方向に読み込みを進めます。一度readしたものは基本的に戻すことはできません。ストリームの終端まで来るとEOFになります。

この性質は他の言語でも一般的なものだと思われます。けれど、複数のコードから一つのストリームを共有するような場合に問題になります。なぜなら先に走ったコードがストリームを読み終わってしまうと、もう一方がストリームを読みに行ってもEOFが帰ってきて読み込めないからです。

これはClackのアーキテクチャにとっては不利な仕様です。

複数のミドルウェアでリクエストボディのストリームを共有していると、前のミドルウェアが全部ストリームを読んでしまった場合にbody-parametersを読めなくなってしまいます。

circular-streamsはこの問題を解決します。

make-circular-streamsでストリームをラップすると、EOFまで読んだあとにまた再び最初からreadすることができます。内部的にバッファを持っており、一度読んだものをまるでストリームから読んだかのように見せます。

(defparameter *stream*
              (flex:make-in-memory-input-stream
               #(72 101 108 108 111)))

(defparameter *circular-stream*
              (make-circular-stream *stream*))

(read-char *circular-stream*)          ;=> #\H
(read-char *circular-stream*)          ;=> #\e
(read-char *circular-stream*)          ;=> #\l
(read-char *circular-stream*)          ;=> #\l
(read-char *circular-stream*)          ;=> #\o
(read-char *circular-stream* nil :eof) ;=> :eof
(read-char *circular-stream*)          ;=> #\H

面白いでしょう?

おわりに

circular-streamsはGitHubで公開されており、現在Starはたったの2です。寂しい。

明日のアドベントカレンダーは7日目のcl-dbiについてです。お楽しみに。

サムライト株式会社を退職します

今年末12月31日でサムライト株式会社を退職することにしました。

2年間という短い期間ではありましたが、Common Lispでの開発を存分にやらせていただきました。おかげで高速なWebサーバーのWooの運用実績ができ、Common Lispが実用に足るだけでなく他の言語を凌ぐパフォーマンスを出せるということが実証できたと思います。

次の雇用先は決まっていないのでこれからのんびり探す予定です。

もし雇ってもいいという企業がいらっしゃれば、退職と雇用先の募集についてをご覧ください。