Haskellプロジェクトのひな形生成ツールを作った

みなさん、Haskellで新しく何かを書き始める時ってどうしてますか。最近の定番ディレクトリ構成は/srcにコード、/testにテストコードってパターンが多いですね。それ自作するの、辛くないですか。

数ヶ月前に何かを作ろうと思いcabal initを実行した時、「もう自分で/src/testディレクトリ作って/test/Spec.hs書くのはウンザリ」と感じ、作ろうとしていた何かをそっちのけでHaskellプロジェクトのひな形生成ツールを作り始めました。それがhiです。

例えばRubyだとbundlerというライブラリを使って、下記のように新しいプロジェクトを生成できます。

$ bundle gem foo
      create  foo/Gemfile
      create  foo/Rakefile
      create  foo/LICENSE.txt
      create  foo/README.md
      create  foo/.gitignore
      create  foo/foo.gemspec
      create  foo/lib/foo.rb
      create  foo/lib/foo/version.rb
Initializating git repo in /Users/fujimura/foo

こういうものを作りたいと思ったわけです。色々考えた結果、下記のようなデザインにすることにしました。

テンプレートをgitリポジトリにするあたりはgrunt-initというJavaScriptのライブラリを参考にしました。対話式インターフェース無し、少ないオプションなどは僕の趣味です。

結果的に、こんな感じで新しいプロジェクトを生成できるようになりました。 テストをHspecで書く場合の定番ディレクトリ構成をデフォルトテンプレートにしています。

$ hi --package-name "foo-bar-baz" --module-name "Foo.Bar.Baz"
Creating new project from repository: git://github.com/fujimura/hi-hspec.git
    create  foo-bar-baz/.gitignore
    create  foo-bar-baz/LICENSE
    create  foo-bar-baz/README.md
    create  foo-bar-baz/foo-bar-baz.cabal
    create  foo-bar-baz/src/Foo/Bar/Baz.hs
    create  foo-bar-baz/src/Foo/Bar/Baz/Internal.hs
    create  foo-bar-baz/test/Foo/Bar/BazSpec.hs
    create  foo-bar-baz/test/Spec.hs

もちろんビルド可能です。失敗するテストケースを一件入れてあります。

$ cabal test
Building foo-bar-baz-0.0.0...
Preprocessing library foo-bar-baz-0.0.0...
In-place registering foo-bar-baz-0.0.0...
Preprocessing test suite 'spec' for foo-bar-baz-0.0.0...
Running 1 test suites...
Test suite spec: RUNNING...

Foo.Bar.Baz
  someFunction
    - should work fine FAILED [1]

1) Foo.Bar.Baz.someFunction should work fine
expected: False
 but got: True

Randomized with seed 4611685480328648939

Finished in 0.0003 seconds
1 example, 1 failure
Test suite spec: FAIL
Test suite logged to: dist/test/foo-bar-baz-0.0.0-spec.log
0 of 1 test suites (0 of 1 test cases) passed.

テスト無し、フラットなディレクトリ構成のテンプレートもあります。

$ hi-m Bar -p bar -r [email protected]:fujimura/hi-flat.git
Creating new project from repository: [email protected]:fujimura/hi-flat.git
    create  bar/.gitignore
    create  bar/LICENSE
    create  bar/Main.hs
    create  bar/Bar.hs
    create  bar/README.md
    create  bar/bar.cabal

テンプレートは自作可能です。Making your own project templateを参照ください。

以下、実装時のこぼれ話です。

コーディング規約はtibbe/haskell-style-guideを踏襲しました。が、あんまり厳密にはやっていません。いわゆるfull importはしないようにしています。自分で書いたコードを読むにあたっても圧倒的にわかりやすかった。

Hackageには、tarballを作ってcabalのコマンドからアップロードするのですが、その前に一応出来たものが正しく動くかテストを実行したいところです。一連の作業をやってくれる便利なスクリプトがHspecのリポジトリにあったので有難く真似させて頂きました。

エディタはVimを使っています。ghc-mod + syntasticで保存されたらコンパイルしてエラーがあれば表示されるようにしてます。あとstylish-haskellでコードのフォーマットをしてます。これはVimからで実行できます

最初スケッチ的な実装をした後、TDDで書き直しました。私、Haskellで関数ごとに細かいユニットテストをするのは割に合わないという認識でして、今回はいちばん外側からのみテストする戦略をとりました。具体的にはテスト用ディレクトリの中で実際に$ hiを実行し、結果を検証するという方法です。テスト後のディレクトリのお掃除などは自分で頑張っていたのですが(bracket_を使えばOK)、途中でHspecにbeforeなどが入ったのでそれを使いました。タイミング良かった。最終的にはメインの関数だけは個別にテストするようにしました*。言うまでもなくこの戦略だと並列実行するとテストがコケるのでそこは残念です。今後は外側からのテストは最小限にしたい。

他にも色々な苦労と迷走がありました。モジュール構成が変、テンプレートに依存したテストケースが多い、など未解決のしょぼいポイントも多々あります。追加予定の機能もいくつか。興味がある方はコードを読んでみてください。リポジトリはhttps://github.com/fujimura/hiです。