Haskellプロジェクトを始めるにあたって

これは一人Computer Scienceアドベントカレンダー 15日目の記事です。


Computer Science何も関係ないけど大丈夫か?(まぁ一応Haskellはテーマの1つであったというアレはあるけど)

今回はHaskellで開発を始める時にいつもやってるセットアップの作業とかの説明をします。 どうも、Haskellerによるstackみたいな周辺ツールの情報の発信が足りてないんじゃないかみたいな噂が流れてきたのでじゃあまぁなんか記事にするかという流れです。

ところでstackの説明はググれば日本語の記事がそれなりにヒットするようになったと思うのでここではあんまり説明しません。

開発環境構築

このセクションは初回のみです。

Haskellのインストール

stackはプロジェクトを管理するツールっていうのかな?まぁビルドツールになったりパッケージマネージャーになったりghcを管理するのに使ったりなんかまぁそういうツールです(なんて言えばいいんだろう)。

linux系なら公式ドキュメントを見ながら次のようにするといいと思います。

  $ curl -sSL https://get.haskellstack.org/ | sh
  # stackのinstall

  $ stack setup
  # GHC(コンパイラ)を入れる

stackを入れてから stack setup でコンパイラが入るのでのんびり待ちます。 ~/.local/bin/ にパスを通しておきます。

エディタ

を使いましょう。その他のエディタは知らない(emacs/intellijのプラグインが特に優秀みたいなので可能ならどっちかを使うのがいいんじゃないでしょうか)。

プロジェクトセットアップ

stack new

[2017/12/16追記]

どうやらstackのデフォルトテンプレートであるnew-templateがいつの間にやらhpackを使うように変わったようです。なので、以下のsimple-hpackは不要で普通に stack new [プロジェクトの名前] とすれば同じものが得られます1

configuration

stack newが終わると必要なファイル群と stack.yamlpackage.yaml が出来ます。 ここでpackage.yamlを適当に編集します(しなくても問題ないです)。

  $ stack build

でビルドできればOKです。

git init

そして私はこのタイミングでgit関連のあれこれをします。 gitignore生成にはgiboを、git強化にgit-flowを使っています。

  $ gibo Haskell > .gitignore
  # .gitignore を作る

  $ echo "*.cabal" >> .gitignore
  # hpackを使っているならcabalは不要

  $ git init && git add . && git commit -m 'initial commit'

  $ git flow init

そして開発へ

以下は必要になった時に必要になった項目を随時行います。

パッケージの追加

パッケージを追加したいときはpackage.yamlのdependenciesに追加します。 バージョンの指定とかできますが別にしなくていいです。

  dependencies:
    - base
    - lens
    - mtl
    ...

hackageのパッケージの追加

上のやり方で上手く行くのはパッケージがstackage(Haskellのパッケージを各バージョンごとに登録しとくところ)にある場合だけです。 hackageにパッケージがある場合はstack.yamlのextra-depsに バージョンまで含めて 書きます。

  extra-deps:
  - package-1.2.3.4
  ...

といっても、これが必要な場合はstack buildした時点でstackがこういう風に書けって教えてくれるので、それをコピペするだけでいいです。

githubのパッケージの追加

例えばgithubにしかパッケージがない場合も同じくstack.yamlのextra-depsに追加します(参照)。

  extra-eps:
  - git: git@github.com:hoge/piyo.git
    commit: commitID

default-extensions

よく使うGHC拡張はpackage.yamlのdefault-extensionsに書いておきます。

  default-extensions:
  - Strict
  - LambdaCase
  - GADTs
  - TemplateHaskell
  ...

まぁこの辺はお好みで。

テスト

好きなものを使えばいいと思います。 私はtastyをよく使います。tastyはtasty-hunittasty-quickcheckなんかがあるので色んなテストのかき方が出来たりtasty-discoverでテストを自動で検出して走らせたりできるので便利です。

package.yamlに

  tests:
    hoge-test:
      source-dirs: test
      main: Driver.hs
      dependencies:
      - base
      - hoge
      - tasty

みたいにして書いて、 test/Driver.hs

  {-#
    OPTIONS_GHC -F
    -pgmF tasty-discover
    -optF --tree-display
  #-}

と書くと使えます。 stack test であとは勝手にテストが走ります。便利。

stackの参照するresolverのバージョンを上げる

stackはresolverで指定されたsnapshotを常に参照します(globalでもlocalでも)。 しばらく開発しているとこれが古くなったりするので、例えば

  $ stack config set resolver lts

とかするとltsの最新版にあげてくれます。

リソースファイルを含める

(例えば)executableなパッケージで実行には特定のリソースファイルが必要とします。 こういう場合はpackage.yamlのdata-filesに書きます。

  data-files:
  - resources/hoge.txt

ところでこのパッケージが testpackage という名前だった場合、

  library:
    other-modules:
    - Paths_testpackage

と書いておくと、

  import Paths_testpackage

  -- data-filesに書いたファイル名からそのファイルのパスを得るには次の関数を使う
  -- getDataFileName :: FilePath -> IO FilePath

みたいなことが出来ます(参照)。

executableをMain.hs以外から実行する

executableでMain.hs以外から実行しようとすると怒られるかもしれませんが -main-is オプションで回避できます。

  executables:
    hoge:
      source-dirs:
      - app
      main: Run.hs
      ghc-options: -main-is Run

おわりに

果たしてこういう記事が求められていたのだろうか、よくわからない(違う気がする)。

こういうのも書いてほしいっていう要望があれば追加するので言ってください。


1

cabalファイルを捨てたいという気持ちはまぁわかるけれどいきなりツールの使い方が変わってしまう大きな変更を入れてしまうのはどうなんだ感もありますね。せめてupgradeするときにリリースノートを表示するとかして欲しいところ。

[/追記]

さてHaskellでプロジェクトを始めます。

  $ stack new [プロジェクトの名前] simple-hpack

simple-hpackというのはテンプレートの名前です。ここではhpackというツールを使っています(私が普段から使っているので)。

(以下昔話なので読まなくてもいいです) さて少しだけ説明をすると、stackという便利ツールが登場する前はみんなcabalというビルドツールを使っていました。cabalはcabalファイルの情報を読み込んでビルドをするのですが、cabalファイルはちょっと面倒な部分があったのでこれをもっと簡単に書けるようにするためにhpackというツールが最近登場しました。 hpackはpackage.yamlをcabalファイルに変換するツールですが、今はstackが公式にhpackをサポートしたので、プロジェクトでpackage.yamlに必要な情報を書くとそれがhpackによってcabalファイルに変換されてcabalによるビルドが走るというところまで全てstackが面倒を見てくれます。

正直好みですが個人的にはhpackの方が楽なのでこれを使うためにここではsimple-hpackのテンプレートを指定しています。

また、以下の説明は全てhpackのpackage.yaml前提です。cabalファイルはまた少し違うので対応表で各自調べてください。