Day 11: Integral
これは fukamachi products advent calendar 2016 の11日目の記事です。
今日はIntegralについて話します。
このライブラリは現在非推奨ですが、歴史として重要なので取り上げます。
Common Lispとデータベース
Cavemanを作ってから課題に感じていたデータベースとの連携についてですが、それまでのCommon Lispコミュニティにもいくつかプロダクトがありました。
一つはElephant。これはバックエンドがBerkeley DBであることが想定された設計で、CLOSオブジェクトを永続化できるという高度な抽象化を行なっています。欠点はBerkeley DBがフリーなライセンスではないこと。バックエンドとしてMySQLやPostgreSQLも選べますが、実行効率は落ちるでしょう。
さらにはRucksack。これもElephantのような永続化ライブラリですが、Common Lispで書かれており依存する外部ライブラリがありません。ただ、開発は止まっており、データベースとしての質にも問題があるようです。
そしてPostmodern。これはいわゆるO/Rマッパーであり、抽象レベルは先の二つよりも低いです。PostgreSQLしか対応していませんがメンテナンスは続いているようですし、コードの質も高く人気もあります。
これらに比べると7日目に紹介したCL-DBIなどはそのずっと後継です。抽象度もずっと低く、文字列をSQLで投げるだけです。
その後SQLをS式にするSxQLを作り、データベース連携の最後のピースとなるのが、今日紹介するのが「Integral」です。
なぜ僕がまた新たにデータベースライブラリを書き揃えることになったかというのは7日目の記事を見てもらうとして、今回はIntegralの設計思想について話をします。
Integralの見た夢
Common Lispが持つ新たなデータベースライブラリはどうあるべきか。
CLOSという他の言語よりも高度なオブジェクトシステムがあるのだからこれを利用しないわけにはいきません。
とはいえ、MySQLやPostgreSQLのような人気のRDBMSをバックエンドとするならElephantほどの永続化機構は作れないでしょう。
マイグレーション機構も重要です。他の言語では当たり前になっている一方で、Common Lispでは標準でマイグレーション機構を持つものがありません。
つまりは、Elephantのような使いやすいインターフェイスだが、一般的なRDBMSをバックエンドとして使え、抽象度が高すぎず、マイグレーション機構を持つもの。これがIntegralが満たすべき条件でした。
ちょうどはてなも退職しようかという頃で京都から関東に戻ってきており、これからCommon Lispでガンガン開発しようという冬休みでした。
公開後はLisp Meetup #13で紹介してブログエントリーを公開し、Shiroさんにコメントをもらったりしてそれなりに悪くない評価でした。
twitter.comShiroさんに「APIはよく考えられてる印象」って言われたのが今年一番うれしかったことです
— fukamachi (@nitro_idiot) 2014年1月20日
マクロレス主義
少し話は逸れますが、重要な話なのでここで触れておくことにします。
Lispの代表的な機能としてよく挙げられるマクロですが、同時に「本当に必要なときしかマクロは使うべきではない」とも言われています。
僕も同意です。実はマクロを使うことよりもマクロを使わないことのほうが難しいんじゃないかと思っているくらいです。
ただし、マクロを使うべきではない理由として「読みづらくなる」ということを挙げている人がいますが、僕はこれには違う意見です。それもありますがより重要なのは、マクロを使うと抽象化レイヤーを適切に分離したり組み合わせたりすることができないことです*1。マクロは関数呼び出しのように他の関数に渡すことはできませんし、マクロの引数を実行時に評価したいなどというときに不便になります。
具体例を挙げるならばSxQLです。SxQLのAPIの多くはマクロが使われています。
(alter-table :tweet (add-column :id :type 'bigint :primary-key t :auto-increment t :first t) (add-column :updated_at :type 'timestamp)) ;=> #<SXQL-STATEMENT: ALTER TABLE tweet ADD COLUMN id BIGINT AUTO_INCREMENT PRIMARY KEY FIRST, ADD COLUMN updated_at TIMESTAMP>
ではもしここで、add-column
のリストを動的に作り、それをalter-table
に渡したいというときはどうでしょう。関数ならばapply
するだけでいいのですが、マクロなのでそれはできません。
SxQLにはその場合のためにより低レイヤーのmake-statement
を使うことができます。実際、マクロを展開すると同じであることがわかります。
;; 上のalter-tableマクロの展開式 (make-statement :alter-table :tweet (add-column :id :type 'bigint :primary-key t :auto-increment t :first t) (add-column :updated_at :type 'timestamp))
マクロは簡易なインターフェイスの提供のために使い、そこから下のレイヤーではマクロは使わない。これならば察しの良いユーザは薄い抽象化レイヤーであることに気づいて安心してハックするでしょうし、簡単なことはマクロで済ませられます。
こういったことは昨日のSxQLの記事に書けばよかったですね。
この思想はClackの開発当時から持っていたもので、当然Integralもこの思想に沿っています。Integralでは特にCLOSとMOP (Meta Object Protocol)を多用して実現しています。MOPはすごいといいつつ実際使うことのほとんどない技術の一つですが、Integralやその後継のMitoでは積極的に使っています。興味があれば実装もご覧ください。
Crane
ちょうど同時期に開発されたO/Rマッパーに「Crane」があります。リリースしたあとに「あ、作ってたんだ」って感じでTwitterで会話しました。
twitter.com@nitro_idiot I suspected you were working on one in secret too (Because cl-dbi and sxql). How about a merger? We can call it Crintegral!
— Fernando Borretti (@eudxa) 2014年1月20日
結局APIも設計も異なるのでプロジェクトマージはできずに、今でもCompeteは続いています。
抽象化の失敗
さて、Integralを作ってしばらく開発を続け、ついに現職のサムライト株式会社でも導入されました。けれどもすぐに現在の設計では抽象化が不十分であることに気づかされることになります。
特にMySQLとPostgreSQLの実装の違いを過小評価しており、PostgreSQLの対応が完全にはできないことがわかりました。
あまりに致命的だったためやむなくPostgreSQLをサポート対象から外し、MySQLとSQLite3をメインターゲットとして公開し続けています。
後継のMitoは主にこの問題を解決するために作られたプロジェクトです。24日目に紹介しますのでそのときに詳しくお話します。
おわりに
IntegralはGitHubで公開されていますが、非推奨なので新規のプロジェクトでは使わないでください。
明日のアドベントカレンダーは12日目のQlotについてです。お楽しみに。
*1:他にも学習コストが上がるという欠点もマクロにはあります。ライブラリ独自のマクロを覚えるより、既に知ってる標準の機能を組み合わせたほうが馴染み深く扱いやすいです。