Haskellでコンパイル時に「(ファイル名):L(現在の行)」を埋め込む


結論

 あるファイルでそのコードの場所を文字列として埋め込みたい場合は、このように実現することができます。

{-# LANGUAGE CPP #-}

let here = __FILE__ ++ ":L" ++ show (__LINE__ :: Int)

(コンパイル時と言ったな、あれは嘘だ。これは多分プリプロセス時に解決されます)

 Haskellでは、関数が単純に例外を出す可能性がある場合はEither eもしくはMaybeを使うと思います。

readFooFromSomewhere :: Bar -> Either Text Foo
readFooFromSomewhere bar =
  let result = {a result of a some calculation}
  case result of
       Baz -> Left "残念"
       Poi -> Right {some}

 これに加えIO文脈下では関数failなどによって、Left eNothingでない、マジモンの例外を出すことができます。
(危険、多用厳禁。main関数以外での使用を推奨しません

failは主に非正常系で使われると思います。

readFooFromSomewhere :: IO ()
readFooFromSomewhere =
  result <- {a result of a some calculation}
  case result of
       Baz -> fail "残念"
       Poi -> return {some}

 failが使われる場所はだいたい非正常系の処理だと思いますので通常は現れないはずですが、
もし現れた場合にissueで報告して欲しい場合はこんな感じにすると思います。

readFooFromSomewhere :: IO ()
readFooFromSomewhere =
  result <- {a result of a some calculation}
  case result of
       -- vvv ここは絶対に通らないよ!もしここを通ったら木の下に埋めてもらっても構わないよ!
       Baz -> fail "Main.readFooFromSomewhere: fatal error! sorry, please report an issue if you see this :("
       -- ^^^
       Poi -> return {some}

 でもいちいち"Main.readFooFromSomewhere"とか書くのは面倒なので、どうせならコンパイル時とかプリプロセス時あたりで、自動で埋め込んで欲しいと思うのは自然なことですよね。

それについて選択肢はいくつか考えられます。

  • プログラムを--profileオプション付きGHCでビルドして、GHC.Stack.HasCallStackcurrentCallStackを使う
  • TemplateHaskellを使う
  • CPPを使う

 今回はCPPを使ってみます。
しかしながらGCCの__FUNC__のようなマクロはないようですので、代わりに__FILE____LINE__で示します。

{-# LANGUAGE CPP #-}

readFooFromSomewhere :: IO ()
readFooFromSomewhere =
  result <- {a result of a some calculation}
  let here = __FILE__ ++ ":L" ++ show (__LINE__ :: Int)
  case result of
       Baz -> fail $ here ++ " : fatal error! sorry, please report an issue if you see this :("
       Poi -> return {some}

できました。

参考