cabal sandbox + LTS Haskell

(2015-08-27 追記) この記事は無視して stack コマンドを使いましょう
---

Haskellでプログラムを楽しむときに外部のライブラリを利用したくなることがある。cabal install コマンドを使えば `~/.cabal/` 以下に外部ライブラリをインストールして利用することができる。

外部ライブラリは他ライブラリの特定のバージョンに依存していることが多い。依存関係が衝突した場合、ライブラリ群を同時にインストールできないことがある。たとえばライブラリAがライブラリXのバージョン1に、ライブラリBがライブラリXのバージョン2に依存する場合には、AとBを同時に `~/.cabal/` にインストールすることはできない。
ひとつのプロジェクトから A と B に依存することはないが、ひとつのプロジェクトでは A に依存し、もうひとつのプロジェクトで B に依存したいことはあるので困る。[これは dependency hell と呼ばれている。](https://www.haskell.org/haskellwiki/Cabal/Survival#What_is_the_difficulty_caused_by_Cabal-install.3F)

Dependency hell を解消する手段として cabal sandbox という機能がある(他にも cabal-dev や hsenv などがあるようだがよく知らない)。cabal sandbox を使うと、任意のディレクトリに外部ライブラリをインストールできる。上の例で A と B は同時にインストールできなかったが、それぞれ別のディレクトリにインストールすれば依存関係が衝突することはない。

プロジェクトごとに cabal sandbox を作ることで dependency hell から逃れることができた。めでたしめでたし。

と思っていたが、インストール時間を結構食うことに気がついた。プロジェクトごとに全ての依存ライブラリをインストールすることになる。インストールにはコンパイルが伴う。大きな依存関係を持つプロジェクトの場合、cabal sandbox への依存ライブラリインストールに数十分かかることもある。つらい。

スタック・オーバーフローで聞いたところ、[Stackage のバージョンごとに sandbox を作り、共有すれば良い](http://ja.stackoverflow.com/a/4333/455)との回答を頂いた。

Haskell のライブラリは Hackage に登録、公開されている。Hackage に登録されているライブラリ群の間でも dependency hell が起きるため、Hackage に登録されている全てのライブラリを同時にインストールすることは出来るとは限らない。
[Stackage](http://www.stackage.org/) はコンパイルできるライブラリのバージョンを Hackage から集めることで、この問題を解決する。Stackageの特定のリリースで提供されるライブラリ群は、全て同時にインストールできることが確認されている。

[LTS (Long Term Support) Haskell](https://www.fpcomplete.com/blog/2014/12/backporting-bug-fixes) は Stackage と関連するプロジェクトで、同じように同時にインストールできるライブラリ群を提供する。Stackage ではリリース間でライブラリの互換性が保証されないが、LTS Haskell ではリリース間でAPIの互換性を担保する。LTS Haskell 1.0 で動いていたプログラムは LTS Haskell 1.1 でも動く（ことを少なくとも目標に掲げている）。
Long Term といわれると Ubuntu の様に数年間サポートしてくれるのかな、と思いがちであるが、現在のサポート期間は3ヶ月である。1年後には LTS Haskell 4.0 が出ている計算だ。将来的にサポート期間を伸ばすことを検討するようだが、現時点ではあまり長くない。

さて、では[2015年1月にリリースされたばかり](https://www.fpcomplete.com/blog/2015/01/announcing-lts-haskell-1-0)の LTS Haskell 1.0 を cabal sandbox と共に試してみよう。
LTS Haskell 1.0 なので `~/.ltshs/1.0/` に sandbox を作ろう。

````
$ mkdir -p ~/.ltshs/1.0/
$ cd ~/.ltshs/1.0/
$ cabal sandbox init --sandbox .
$ curl -O http://www.stackage.org/lts/1.0/cabal.config
````

なおデフォルトでは、Hackageに含まれる全てのパッケージがインストール可能であり、インストールが可能なことを保証するのは LTS Haskell に登録されているパッケージのみというポリシーになっている。登録されているパッケージ以外はインストールもしたくない場合には、ダウンロードした `cabal.config` 内の `remote-repo:` の行をコメントインすれば良いようだ。

````
-- To only use tested packages, uncomment the following line:
-- remote-repo: stackage-lts-1.0:http://www.stackage.org/snapshot/lts-1.0
````

これで LTS Haskell 1.0 の sandbox が `~/.ltshs/1.0/` にできた。
LTS Haskell 1.0 を利用するプロジェクトでは次のようにする。

````
$ cd YOUR_PROJECT/
$ cabal sandbox init --sandbox ~/.ltshs/1.0/
````

これで `YOUR_PROJECT/` も `~/.ltshs/1.0/` の sandbox を使うようになった。
他のプロジェクトでも同様に設定すれば、コンパイル済みのライブラリは再利用しつつ、依存関係の衝突を起こさない幸せな生活が送れるはずだ。

…とここまで書いて [LTS Haskell のページ](http://www.stackage.org/lts)をみたら、 LTS Haskell 1.2 が出ていた。 LTS Haskell 1.0 から 1.2 への乗り換えも練習してみよう。

````
$ mkdir -p ~/.ltshs/1.2/
$ cd ~/.ltshs/1.2/
$ cabal sandbox init --sandbox .
$ curl -O http://www.stackage.org/lts/1.2/cabal.config
````

これで LTS Haskell 1.2 の sandbox ディレクトリができた。
プロジェクトの参照先もこちらに変える。

````
$ cd YOUR_PROJECT/
$ cabal sandbox init --sandbox ~/.ltshs/1.2/
````

以前 `cabal sandbox init --sandbox ~/.ltshs/1.0/` を実行して LTS Haskell 1.0 を参照するようにしていたプロジェクトで上記コマンドを実行したところ、問題なく `~/.ltshs/1.2/` を参照するように変更できた。
1.0 と 1.2 は互換性があるので、ソースコード書き換えをすることがなく乗り換え可能なはずだ。