Haskell Stack とは何をするツールなのか

この記事では、Haskell Stack についての初歩的な説明をします。

（2022-11-20補足）現在は、GHC や Stack のインストールには [GHCup](https://www.haskell.org/ghcup/) を使うのが主流になっています。

# はじめに

いま Haskell の開発環境を導入するといえば [Stack](https://haskellstack.org/) だろうと思います。
これから Haskell の勉強を始めようと思うなら、まずは Stack の使い方を覚えることになります。

Stack は便利なツールなのですが、個人的な印象としては、最初のとっつきが悪いと思っています。

僕が初めて Stack に接したときは、「これはいったい何をするツールなのだろう」とモヤモヤしました。
ひとつのツールでいろいろな役割をこなすので、かえって何のツールか分からないのです。
また、コマンドを実行しても、実際に何が起こっているのかがよく分からないのもモヤモヤする点でした。

そういう初歩的な説明はあまり誰もしてくれません。
そこで、試しに説明をしてみようと思いました。

なお、Stack 自体のインストール方法については本記事では割愛します。
公式ドキュメントの [Install/upgrade](https://docs.haskellstack.org/en/stable/install_and_upgrade/) に書いてありますし、それ以外にもあちこちで解説が見つかります。

# Stack でできること

主に、以下の3つをこなすツールです。

* (1) Haskell コンパイラをパソコンにインストールする。
* (2) Haskell 製のツールを、ビルドしてパソコンにインストールする。
* (3) Haskell で開発する際のプロジェクトを新規作成したり、プロジェクトをビルドしたりする。

Stack について把握していくと、(3) がメイン機能で (1) や (2) はその派生的な機能だと気づきます。
しかし、まずは (1) や (2) から把握していくのが良いと考えます。

# (1) Haskell コンパイラをパソコンにインストールする

これを行うのが `stack setup` コマンドです。

```
stack setup
```

実際に `stack setup` コマンドを実行すると、おもむろにコンパイラである GHC のダウンロードとインストールが始まります。
初めて実行すると、ここでいきなり時間がかかることに驚くかと思います。しばらく待ちましょう。

無事に成功すると、次のコマンドでコンパイラである GHC を起動できます。

```
stack ghc
```

また、`stack ghc -- --version` で GHC のバージョンを表示できます。
なお、`ghc` コマンドにオプションを渡すために `--` を間にはさみます（`--` が間にない場合は `stack` コマンドへのオプションとして扱われます）。

さらに、`stack ghci` で対話環境 GHCi が起動できます。

```
stack ghci
```

Haskell の入門書などを読むなら、GHCi が使えればとりあえずは足りるかと思います。

## インストール先とアンインストール方法

`stack setup` で長々とダウンロード＆インストールしたものは `~/.stack` ディレクトリ以下に格納されています。

Haskell 開発環境がいらなくなってアンインストールする場合は、`~/.stack` ディレクトリを丸ごと削除してしまうのが簡単です。

## 補足

もし `stack setup` が途中で失敗した場合は、もう一度 `stack setup` を実行しなおせば再度ダウンロード＆インストールを試みます。

`stack setup` が無事終了した状態でもう一度 `stack setup` を行うと、すでにダウンロード＆インストール済みであるため、すぐに終了します。

# (2) Haskell 製のツールを、ビルドしてパソコンにインストールする

これを行うのが `stack install` コマンドです。

例えば、Haskell の開発補助ツールである stylish-haskell や HLint をインストールするには、次のコマンドを実行します。

```
stack install stylish-haskell
stack install hlint
```

実際にこれらの `stack install` コマンドを実行すると、依存パッケージのダウンロードとビルドが始まります。
結構たくさんのパッケージがビルドされる場合もあり、やはり驚くかと思います。しばらく待ちましょう。

無事に成功すると、`~/.local/bin` ディレクトリ以下に実行ファイルがインストールされています。
この `~/.local/bin` に PATH が通してあれば、インストールしたツールをシェル上で実行できるというわけです。

## インストール先とアンインストール方法

`stack install` でダウンロード＆ビルドしたパッケージは `~/.stack` ディレクトリ以下にあります。
そして、最終的にできた実行ファイルを `~/.local/bin` ディレクトリにコピーするのが `stack install` コマンドの仕事です。

アンインストールする場合は `~/.local/bin` ディレクトリに作成された実行ファイルを直接削除します。
`~/.stack` ディレクトリ以下にもビルドしたものがあるのですが、ビルドしたものを選んで削除するのは大変なので、そのまま放置するか `~/.stack` ディレクトリごと削除するかになります。

なお、`~/.local/bin` は環境によって異なるパスである場合があります。実際のパスは `stack path --local-bin` コマンドで取得できます。

## 補足

もし `stack install` が途中で失敗した場合は、もう一度 `stack install` を実行しなおせば再度ダウンロード＆インストールを試みます。

`stack install` が無事終了した状態でもう一度 `stack install` を行うと、すでにダウンロード＆インストール済みであるため、すぐに終了します。

上記では stylish-haskell や HLint を例として挙げましたが、開発補助ツールでなくとも、それ以外の Haskell 製ツールをインストールすることにも使えます。
例えば、Haskell 製のツールとして有名なもののひとつ、Pandoc をインストールするには、`stack install pandoc` を実行します。
（とはいえ、Pandoc をインストールしたいなら、ビルド済みバイナリをインストールした方が早いでしょうが）

# (3) Haskell で開発する際のプロジェクトを新規作成したり、プロジェクトをビルドしたりする

最初にも書きましたが、これが Stack のメイン機能です。

この機能については、メイン機能だけあってあちこちで解説が見つかります。そのため、ここで詳しく書くことはしません。主なコマンドを挙げておきます。

* `stack new` : プロジェクトをテンプレートから新規作成する。
* `stack init` : プロジェクトをテンプレートを使わずに新規作成する。
* `stack setup` : プロジェクトで指定されたバージョンのコンパイラや基本パッケージをインストールする。
* `stack build` : プロジェクトをビルドする。
* `stack install` : プロジェクトをビルドして local-bin にインストールする。

プロジェクトを作成すると、プロジェクトディレクトリ内に `stack.yaml` という設定ファイルができます。

なお、プロジェクトを作成して作業する場合、プロジェクトディレクトリ内に `.stack-work` というディレクトリができます。ダウンロードやビルドの際には、`~/.stack` だけでなく `.stack-work` も使われます。

# グローバルプロジェクト

(3) のコマンドを見ると気づきますが、(1) や (2) で使った `stack setup` や `stack install` がプロジェクトのコマンドとして存在しています。

実のところ、Stack のコマンドはみんな、プロジェクト内で使用するコマンドです。しかし、(1) や (2) の操作では、特にプロジェクトを作成したりはしませんでした。プロジェクトのない場所で Stack のコマンドを実行した場合、それはグローバルプロジェクトを使って実行したものとみなされます。

グローバルプロジェクトは `~/.stack/global-project` ディレクトリにあります。この中に、通常のプロジェクトと同様に `stack.yaml` ファイルや `.stack-work` ディレクトリがあります。

最初に、(3) がメイン機能で (1) や (2) はその派生的な機能だと書きました。これは、(1) や (2) はグローバルプロジェクトを使っているという以外は (3) の機能とほぼ同じであるという意味でした。

# パッケージ

ここまでで、Stack の機能がなんとなく分かってきたのではないかと思います。

ただ、ひとつ重要な点を抜かして書いてきました。それはパッケージについてです。

Haskell では、様々なライブラリやツールがパッケージという形で提供されています。
そして、ひとつのパッケージが別のパッケージの機能を利用することが多いです。
`stack install` のときにいくつもの依存パッケージがダウンロード＆ビルドされたのを覚えているかと思います。

このパッケージという仕組みは強力ではありますが、一方で依存関係がややこしくなりがちです。

ここで、パッケージに関する用語を挙げておきます。

* Cabal : パッケージの仕様
* Hackage : 多数の Cabal パッケージを集めたリポジトリ
* Stackage : 依存関係を解決したパッケージ集を管理する場所
* snapshot : Stackage で管理されているパッケージ集

Stack は、どの snapshot を使うかを `stack.yaml` の `resolver` の項目で指定します。
これによって、常に特定の snapshot を使うため、依存関係に悩まされずに安定してビルドできます。

# おわりに

以上、Haskell Stack についての初歩的な説明をしてみました。

これから Haskell を勉強してみたい人に少しでも役立てば幸いです。