Subscribed unsubscribe Subscribe Subscribe

undefinedの代わりに$notImplementedを使おう

Haskell

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としておけば、コンパイルでこけてくれる