はじめに
Haskell を始めて数ヶ月の者です。入門書のサンプルコードを試したり、小さなプログラムを書いたりしながら、少しずつ、Haskell への理解を深めているところです。その際にはもちろん、Stack を使用しています。
ただ、この Stack というツール、私のように素の GHC や Cabal を使った開発の経験がない人間にとって、各コマンドや設定の意味をちゃんと理解して使うことが容易ではありません。中でも、パッケージに関する設定等はややこしく、何度も同じ疑問をググってしまいました……。
本稿は、そんな Stack を使いこなす上で必要となるパッケージ(やその構成要素であるモジュール)についての基礎知識をまとめたものです。HOWTO やチートシートではなく、「Haskell におけるコード分割や再利用の基本コンセプト」の理解の助けになるような説明に注力してみました。
モジュール
モジュール
Haskell におけるモジュールとは、型や値などの集まりです。Haskell のプログラムはモジュールの集まりです。モジュールは他のモジュールで定義された型や値をインポートして使用することができます。
JavaScript 等のモジュールと異なり、Haskell のモジュールはファーストクラスオブジェクトではありません。したがって、モジュールをプログラム内で変数に代入したり、動的に作成や変更を行うようなことはできません。コードから見た Haskell のモジュールの役割は、名前空間の制御に他なりません。
Haskell のモジュールとソースファイルは1対1の関係にあります。1つのソースファイルに複数のモジュールを定義したり、複数のソースファイルにまたがって1つのモジュールを定義するようなことはできません。
モジュールの名前
通常、モジュール名とソースファイル名(の拡張子を除いた部分)は一致させる必要があります。例えば、ソースファイル Foo.hs で定義するモジュールの名前は Foo となります。例外として、他のモジュールからインポートされることがないモジュールに限り、モジュール名とは異なる任意のソースファイル名で定義することもできます。なお、モジュール名は大文字で始まる必要があります。
モジュール名中の . はソースファイルのパスの区切り文字に変換して解釈されます。例えば、モジュール Foo.Bar.Baz を定義する場合、ソースファイルは(Unix 系 OS 上では)Foo/Bar/Baz.hs として配置することになります。ここで、モジュール名はあくまでも Foo.Bar.Baz であることに注意してください。Baz は単なるモジュール名の一部分でしかありません。もし、モジュール Foo.Bar.Qux があったとすると、この2つのモジュールのソースファイルは同じディレクトリに置かれることになりますが、何か特別な関係が発生するわけではありません。Foo.Bar.Qux にて Foo.Bar.Baz をインポートする場合は import Foo.Bar.Baz という宣言が必要です。import Baz や import ./Baz といったような相対的な記述はできません。. による階層化は、ソースファイルを人間にとってわかりやすくする配置するためのものでしかありません。
モジュール Main
Haskell のプログラム実行時のエントリーポイントは Main というモジュールの main という関数です。そのため、実行プログラムには必ず、main という名前の関数を定義した Main という名前のモジュールが必要となります。
多くの場合、Main が他のモジュールからインポートされることは無いため、Main.hs とは異なるソースファイル名で定義することもよくあります。Main として扱ってほしいソースファイル名はコンパイル時に指定します。
パッケージ
パッケージ
パッケージとは、Haskell で作成されたソフトウェアを管理するための仕組みです。パッケージは主に次の2つの目的のために使用されます。
- 他のプログラムから利用可能なモジュールの集まり(ライブラリ)を配布する
- Haskell で作成されたアプリケーション(モジュール
Mainを含むプログラム)を配布する
ライブラリとアプリケーションの両方を含むパッケージも存在します。
パッケージの仕様はモジュールとは異なり、Haskell の言語仕様で定義されているわけではありません。GHC や Cabal の機能の一つとして実装されている、事実上の標準という位置付けです。この標準仕様のことを特に Cabal パッケージとも呼びます。通常、パッケージと言えば Cabal パッケージのことを指します。
パッケージの名前、バージョン、パッケージ ID
パッケージはモジュールの集まりではありますが、その名前は特にモジュール名と連動させる必要はありません。モジュール A.B.C を含むパッケージの名前が foo でも問題ありません。ただし、既存のパッケージとの重複は避けねばなりません。ちなみに、似たような名前のモジュールが別のパッケージに分かれていることもよくあります。例えば、Data.Functor.Const モジュールは base パッケージに、Data.Functor.Constant モジュールは transformers パッケージに定義されています。
パッケージに割り振るバージョン番号に関しては、PVP (Haskell Package Versioning Policy) という規約が定められており、一般に公開する場合はそのルールに従うことが推奨されています。詳細は Haskell PVP Specification に解説があります。ちなみに、 PVP は SemVer(Semantic Versioning)とはルールが微妙に異なりますので、注意が必要です。
パッケージは名前とバージョンの組み合わせで識別されます。これをパッケージ ID と呼びます。パッケージ ID はパッケージ名とバージョン番号文字列を - で結合した形式で表されます。例えば、パッケージ hspec のバージョン 2.5.7 のパッケージ ID は hspec-2.5.7 となります。
パッケージのコンテンツ
パッケージには以下のようなファイルが含まれます。
- 配布するモジュールのソースコードファイル
- パッケージのメタ情報を定義する
.cabalファイル - パッケージのビルドに使用する
Setup.hsファイル - その他、README やマニュアル等のドキュメント、テスト実行に必要なファイル等
これらのファイルを標準的な tar ボール形式のアーカイブファイル(圧縮済み)にまとめたものが、パッケージの配布に使われます。例えば、パッケージ ID hspec-2.5.7 のパッケージは hspec-2.5.7.tar.gz として配布されています。
通常、配布するパッケージにはモジュールのソースコードが含まれており、コンパイルはパッケージのインストール時に行われます。そのため、パッケージは基本的にプラットフォーム非依存となります。
パッケージの依存関係
パッケージが他のパッケージに依存する(他のパッケージに含まれるモジュールをインポートしている)場合、その依存関係を明示的に .cabal ファイルに宣言する必要があります。この明示的な宣言により、パッケージのインストール時に、依存するパッケージも自動的にインストールすることができます。
Hackage
Hackage は haskell.org が運用するパッケージリポジトリです。Perl の CPAN や Python の PyPI に相当するもので、数千ものパッケージが登録、公開されています。
GHC
GHC
GHC(Glasgow Haskell Compiler)は事実上の標準として、最も広く使われている Haskell の処理系です。GHC を構成する主要なコンポーネントは以下の2つです。
- コンパイラである GHC(
ghcコマンド) - REPL である GHCi(
ghciコマンド)
他にも、Haskell のソースファイルをコンパイルすることなく実行可能なインタプリタ runghc や、パッケージの管理に使用する ghc-pkg 等のツールも組み込まれています。
また、GHC には多数のパッケージが同梱されています。標準ライブラリとも呼ばれるこれらのパッケージの一覧や詳細は Haskell Hierarchical Libraries にて確認することができます。
コンパイル
Haskell のソースコードをコンパイルするには ghc コマンドを使用します。
$ ghc Foo.hs
上記コマンドの実行により、モジュール Foo をコンパイルしたオブジェクトコードファイル Foo.o と、モジュールの依存関係情報を記述したインタフェースファイル Foo.hi が生成されます。
実行可能プログラムのビルド
関数 main を含むモジュール Main をコンパイルすると、自動的にリンクまで行われ、実行可能ファイルが生成されます。
例えば、以下のような内容のソースファイル hello.hs があるとします。
main = putStrLn "Hello, World!"
この hello.hs を以下のようにコンパイルします。
$ ghc hello.hs
hello.hs は main 関数を含むので、Main モジュールと判定されます。コンパイルの結果、hello.o と hello.hi に加え、実行可能ファイル hello(Unix 系 OS の場合。Windows の場合は hello.exe)が生成されます。
$ ./hello
Hello, World!
複数モジュールからなるプログラム
GHC はコンパイルするモジュールがインポートしているモジュールのコンパイルも自動的に行います。複数のモジュールからなるプログラムも、コンパイル対象として Main モジュールのソースファイルだけ指定すれば、import のチェーンの解析が行われ、自動的にすべてのモジュールのコンパイル、リンク、実行可能ファイルの生成まで完了します。そのため、一般的な構成のプログラムであれば、make のようなツールを導入することなく、GHC のみでビルド作業が完結します。
モジュール検索パス
GHC がモジュールの import チェーンの解析を行う際に、利用可能なモジュールの検索対象となるのは以下の2つです。
- モジュール検索パス
- モジュールのソースコード(またはオブジェクトコード)が格納されているディレクトリのパス
-
ghcのコマンドラインオプション-iで指定 - 複数のパスを指定可能
- デフォルトは
.(カレントディレクトリ)
- インストール済みパッケージ
- パッケージデータベースに登録されているパッケージ
パッケージデータベース
インストールしたパッケージの情報が格納されているディレクトリをパッケージデータベースと呼びます。通常、package.conf.d という名前で作成されます。原則として、GHC は次の2つのパッケージデータベースを参照します。
- グローバルパッケージデータベース
- システム全体で共通のパッケージデータベース
- 例:
/usr/lib/ghc-<x.y.z>/package.conf.d
- ユーザーパッケージデータベース
- 各ユーザーごとにインストールしたパッケージの格納に使用するパッケージデータベース
- 例:
$HOME/.ghc/<arch-os-version>/package.conf.d
ghc のコマンドラインオプション -package-db や環境変数 GHC_PACKAGE_PATH の指定により、参照するパッケージデータベースを追加、変更することもできます。
パッケージデータベースのディレクトリに配置されるのは、パッケージのメタ情報を記述した .conf ファイルです。その中にパッケージの実体となるファイルの場所等が記述されています。
ghc-pkg
パッケージデータベースに対し、パッケージの追加や削除等の操作を手動で行うことは基本的にはありません。ghc-pkg コマンドや Cabal を使用します。
ghc-pkg はパッケージデータベースを管理するための GHC 付属のツールです。パッケージの追加や削除、検索等を実行できます。ただし、パッケージの追加に関しては、より高位なインタフェースである Cabal が使われることが多く、ghc-pkg を直接、使用することは少ないでしょう。以下が、ghc-pkg でよく使われるコマンドです。
-
ghc-pkg list: パッケージデータベースにインストールされているパッケージの一覧を出力する -
ghc-pkg describe <package>: 指定したパッケージの詳細情報を出力する -
ghc-pkg find-module <module>: 指定したモジュールを含むパッケージを検索する -
ghc-pkg unregister <package>: パッケージデータベースからパッケージを削除する
Cabal
Cabal
Cabal とはパッケージ管理ツールであり、また、Haskell のパッケージシステム全体を表す名称でもあります。
パッケージ管理ツールとしての Cabal には以下の3つの側面があります。
- 既存パッケージの管理(インストール等)
- 自作パッケージの管理(設定、ビルド等)
- サンドボックス(作業プロジェクトごとに隔離された実行環境)の管理(作成、破棄等)
Cabal の機能の核となる部分は Cabal パッケージとして GHC に同梱されています。しかし、コマンドラインからそれらを利用するための cabal コマンドは cabal-install パッケージに含まれており、別途インストールする必要があります。
パッケージのインストール
パッケージのインストールには cabal install コマンドを使用します。
$ cabal install <package>
cabal install はデフォルトで Hackage をパッケージのインストール元として使用します。オプションの指定により、他のアーカイブリポジトリやローカルのディレクトリを指定することもできます。
cabal install がパッケージをインストールする先のデフォルトはローカルパッケージデータベースです。オプションの指定により、グローバルパッケージデータベースや他のパッケージデータベースへの変更も可能です。また、サンドボックス機能を使用している際は、サンドボックス内のパッケージデータベースにインストールすることも可能です。
インストール時にはパッケージの依存関係がチェックされ、依存するパッケージがパッケージデータベースにない場合は、自動的にそのパッケージもインストールされます。
パッケージをインストールすると、パッケージに含まれているライブラリやアプリケーションが使用可能になります。アプリケーションに含まれるコマンドはデフォルトでは $HOME/.cabal/bin にコピーされます。これは設定ファイル($HOME/.cabal/config)で変更可能です。
cabal install を使用するのは基本的にはアプリケーション型パッケージのインストール時です。ライブラリ型パッケージの場合は、開発しているパッケージの依存関係として宣言することで、そのビルド時に自動的にインストールされます。そのため、手動でインストールする機会は滅多にないでしょう。
パッケージの作成
パッケージを作成するには、パッケージに含めるソースコード等に加え、以下の2つのファイルが必要となります。
- パッケージのメタ情報を定義する
.cabalファイル - パッケージのビルドに使用する
Setup.hsファイル
これらは手動でも作成できますが、cabal init コマンドを使用して生成することもできます。cabal init を実行し、対話形式でパッケージのメタ情報を入力すると、自動的に .cabal ファイルやSetup.hs ファイルが生成され、パッケージのビルドに必要な最低限のファイルが揃います。.cabal ファイルはパッケージの構成に応じて、さらに詳細な項目を設定する必要があるでしょう。一方、Setup.hs ファイルは通常、編集の必要はありません。
ファイルが揃ったら、以下のコマンドでパッケージのビルドを行います。
$ cabal configure
$ cabal build
ビルドに成功したら、以下のコマンドでパッケージの作成を行います。
$ cabal sdist
初期設定ではカレントディレクトリ下の dist ディレクトリ内にパッケージ(<package-id>.tar.gz)が作成されます。
また、Hackage に開発者アカウントが登録済みであれば、cabal upload コマンドで作成したパッケージをアップロードし、公開することもできます。
.cabal ファイル
.cabal ファイルはパッケージの設定やメタ情報を定義するテキストファイルです。通常、ファイル名は <package-name>.cabal となります。
.cabal ファイルで設定する項目は多岐に渡ります。代表的な項目は以下の通りです。
- パッケージ名
- バージョン
- ライセンス情報
- 作者情報
- 依存するパッケージ
- パッケージの構成(ライブラリ、アプリケーション、あるいはその両方)
- ライブラリの場合、パッケージが公開するモジュール
- ビルドのオプション
- test や benchmark 等の特別なビルドターゲットの設定
Stack
Stack
さて、ようやく、Stack でのパッケージの取り扱いについての解説に必要な知識が出揃いました!
……が、長くなりましたので、この続きはまた別の記事としたいと思います。