Haskellで一からコードを書くとき、よく型だけ書いて実装はundefinedにすることがあります。これはとても便利な常套手段なんですが、規模が大きくなってきたり、undefinedな数が多くなってくると不便に思うこともあります。
undefinedとplaceholders
一通り関数とその型の概要ができてきて、小さく動かせる範囲で実装していきましょうという段階に入ると、動かしながら実装したくなるものです。
このとき未実装の部分をundefinedにしておくと、実行時には例外が上がります。残念なことにこの例外はどこから上がったものなのかエラーメッセージを見てもわかりません。
例えばこんな感じ…
Foo: Prelude.undefined
これじゃつらいので行番号を出せるようにします!というのがplaceholdersライブラリです。
使い方は簡単で、
f :: MonadFooBar m => m [a] f = undefined
みたいにしていたところを
{-# LANGUAGE TemplateHaskell #-} import Development.Placeholders f :: MonadFooBar m => m [a] f = $notImplemented
に変えるだけ。ほかにもtodo専用もあります。
g = $(todo "ご飯食べてから実装する")
undefinedとの違いは、実行時に行番号を出してくれるだけでなく、コンパイル時に警告を出してくれる*1ので、git grep undefinedしなくても実装し忘れることがないという利点があります。こんな感じ。
src/FooBar.hs:38:7: Unimplemented feature src/FooBar.hs:39:9: TODO: ご飯食べてから実装する
仕組みも単純で、Template Haskellでもって行番号を入れているだけです。
欠点と言えば、各ファイルにTemplateHaskellの指定とimport文を書く必要があることです。前者はcabalファイルにextensionsで指定すればいいものの、後者はどうしようもありません。
プリプロセッサplaceholders
そこで全モジュールに自動的にimport Development.Placeholdersを挿入するGHCプリプロセッサを書きました。これを使うと、ソースコード中にimport文を書かなくてもよくなります。
pull requestがまだ取り込まれてないので、インストールにはGitHubにあるソースが必要です。
git clone git://github.com/maoe/placeholders.git cd placeholders git checkout feature/preprocessor cabal install # もしくはcabal-dev installなどお好みで
これで~/.cabal/configに指定されているexecutableのインストール先にplaceholdersというバイナリがインストールされます。PATHが通っている事を確認しておきましょう。
あなたのプロジェクトで実際に使うには、cabalファイルにはこんな風に指定するといいでしょう。
flag devel
default: False
library
...
if flag(devel)
extensions: TemplateHaskell
ghc-options: -F -pgmF placeholders
build-depends: placeholdersこうすることで、cabal buildするとGHCが前処理の段階でplaceholdersを実行し、各モジュールの適切な場所に
import Development.Placeholders
という行を追加してくれるので、あとは必要なところに
f = $notImplemented
だの
g = $(todo "やる気が出たら実装する")
など書いていくだけです。
既知の問題
現時点では、このプリプロセッサはいくつか問題があります。pull request歓迎です。
- CPPを有効にしていると、エラーメッセージの行番号がずれる
- 何も考えずに全開importするので、notImplementedとかtodoなどの識別子がコンフリクトする可能性がある。
- UnicodeSyntaxには対応してない。
書いた本人がまだそれほど使い込んでないので使い勝手がどうか分かりませんが、もしかしたら便利かもしれません。
*1:productionでは-Werrorとしておけば、コンパイルでこけてくれる