本の虫

著者:江添亮
ブログ: http://cpplover.blogspot.jp/
メール: boostcpp@gmail.com
Twitter: https://twitter.com/EzoeRyou
GitHub: https://github.com/EzoeRyou

アマゾンの江添のほしい物リストを著者に送るとブログ記事のネタになる

筆者にブログのネタになる品物を直接送りたい場合、住所をメールで質問してください。

Haskellのエラーメッセージについて

Haskellの実装であるGHCのエラーメッセージがわかりにくい。

例えば以下のコードがあるとしよう。

f p as@(x:xs) =
    if p x 
    then f p xs
    else as

main = return ()

この関数fはdropWhileと名付けてもいい関数だ。この関数の型は( t -> Bool ) -> [t] -> [t]だ。

ところで、この関数をうっかり書き間違えてしまい、then f p xsとすべきところを、第一引数のpredicateを渡し忘れ、then f xsとしてしまった場合を考えよう。

f p as@(x:xs) =
    if p x
    then f xs
    else as

main = return ()

このコードをGHC 8.0.2でコンパイルすると以下のようなエラーメッセージが表示される。


[1 of 1] Compiling Main             ( prog.hs, prog.o )

prog.hs:1:1: error:
    • Couldn't match type ‘t -> Bool’ with ‘[t]’
      Expected type: [t] -> [t]
        Actual type: (t -> Bool) -> [t] -> [t]
    • Relevant bindings include f :: [t] -> [t] (bound at prog.hs:2:1)

このエラーから読み取れる情報は、(t -> Bool)型は[t]型にマッチできないということだ。f p xsとすべきところをf xsとしてしまったのだから当然の話だ。pは(t -> Bool)でxsは[t]だ。

だが、このエラーメッセージからはどこの箇所が悪かったのか全然わからない。

しかし、このコードをGHC 7.10.3でコンパイルすると、以下のようなとてもわかり易いエラーメッセージが表示される。

prog.hs:3:10:
    Couldn't match expected type ‘[t]’ with actual type ‘[t] -> [t]’
    Relevant bindings include
      xs :: [t] (bound at prog.hs:1:11)
      x :: t (bound at prog.hs:1:9)
      as :: [t] (bound at prog.hs:1:5)
      p :: t -> Bool (bound at prog.hs:1:3)
      f :: (t -> Bool) -> [t] -> [t] (bound at prog.hs:1:1)
    Probable cause: ‘f’ is applied to too few arguments
    In the expression: f xs
    In the expression: if p x then f xs else as

prog.hs:3:12:
    Couldn't match expected type ‘t -> Bool’ with actual type ‘[t]’
    Relevant bindings include
      xs :: [t] (bound at prog.hs:1:11)
      x :: t (bound at prog.hs:1:9)
      as :: [t] (bound at prog.hs:1:5)
      p :: t -> Bool (bound at prog.hs:1:3)
      f :: (t -> Bool) -> [t] -> [t] (bound at prog.hs:1:1)
    In the first argument of ‘f’, namely ‘xs’
    In the expression: f xs

問題は3行目の10文字目と12文字目にあることがわかり、関連するbindingが一覧表示され、問題のある式とその式を含む式まで表示してくれる。これならわかりやすい。バージョンアップしてわかりにくくなるとはどういうことだ。

GHC 8.2.1ではエラーメッセージが改良されたそうだ。果たして直っているだろうか。

https://wandbox.org/permlink/leQ7uQaoN1eqBPLS

prog.hs:1:1: error:
    • Couldn't match type ‘t -> Bool’ with ‘[t]’
      Expected type: [t] -> [t]
        Actual type: (t -> Bool) -> [t] -> [t]
    • Relevant bindings include f :: [t] -> [t] (bound at prog.hs:1:1)
  |
1 | f p as@(x:xs) =
  | ^^^^^^^^^^^^^^^...

なるほど、Clangのプリティなエラーメッセージを真似ようという意思は感じられる。しかしその箇所は関数を宣言している箇所だ。関数の引数が間違っている箇所を指定してくれなければ意味がない。なぜGHC 7でできていたことがGHC 8でできなくなっているのだ。

Wandboxで最新のHEADも試したが、この問題はまだ解決していなかった。

さて、fの型を明示的に書くとエラーメッセージが変わるらしい。早速試してみよう。

f :: (t -> Bool) -> [t] -> [t]
f p as@(x:xs) =
    if p x
    then f xs
    else as

main = return ()

これをGHC 8.0.2でコンパイルすると以下のようなメッセージが表示される。

https://wandbox.org/permlink/307bjIWpMGZ3jJhO

prog.hs:4:10: error:
    • Couldn't match expected type ‘[t]’
                  with actual type ‘[t0] -> [t0]’
    • Probable cause: ‘f’ is applied to too few arguments
      In the expression: f xs
      In the expression: if p x then f xs else as
      In an equation for ‘f’: f p as@(x : xs) = if p x then f xs else as
    • Relevant bindings include
        xs :: [t] (bound at prog.hs:2:11)
        x :: t (bound at prog.hs:2:9)
        as :: [t] (bound at prog.hs:2:5)
        p :: t -> Bool (bound at prog.hs:2:3)
        f :: (t -> Bool) -> [t] -> [t] (bound at prog.hs:2:1)

prog.hs:4:12: error:
    • Couldn't match expected type ‘t0 -> Bool’ with actual type ‘[t]’
    • In the first argument of ‘f’, namely ‘xs’
      In the expression: f xs
      In the expression: if p x then f xs else as
    • Relevant bindings include
        xs :: [t] (bound at prog.hs:2:11)
        x :: t (bound at prog.hs:2:9)
        as :: [t] (bound at prog.hs:2:5)
        p :: t -> Bool (bound at prog.hs:2:3)
        f :: (t -> Bool) -> [t] -> [t] (bound at prog.hs:2:1)

ようやくGHC 7に戻ってきた。GHCはfの型を正しく推定できているのに、なぜ型tを明示的に書かなければ親切なエラーメッセージを出してくれないのだ。不親切にもほどがある。

さて、ではエラーメッセージが親切になったというGHC 8.2.1ではどうか。

https://wandbox.org/permlink/8j00LitIvUUuzDTM

prog.hs:4:10: error:
    • Couldn't match expected type ‘[t]’
                  with actual type ‘[t0] -> [t0]’
    • Probable cause: ‘f’ is applied to too few arguments
      In the expression: f xs
      In the expression: if p x then f xs else as
      In an equation for ‘f’: f p as@(x : xs) = if p x then f xs else as
    • Relevant bindings include
        xs :: [t] (bound at prog.hs:2:11)
        x :: t (bound at prog.hs:2:9)
        as :: [t] (bound at prog.hs:2:5)
        p :: t -> Bool (bound at prog.hs:2:3)
        f :: (t -> Bool) -> [t] -> [t] (bound at prog.hs:2:1)
  |
4 |     then f xs
  |          ^^^^

prog.hs:4:12: error:
    • Couldn't match expected type ‘t0 -> Bool’ with actual type ‘[t]’
    • In the first argument of ‘f’, namely ‘xs’
      In the expression: f xs
      In the expression: if p x then f xs else as
    • Relevant bindings include
        xs :: [t] (bound at prog.hs:2:11)
        x :: t (bound at prog.hs:2:9)
        as :: [t] (bound at prog.hs:2:5)
        p :: t -> Bool (bound at prog.hs:2:3)
        f :: (t -> Bool) -> [t] -> [t] (bound at prog.hs:2:1)
  |
4 |     then f xs
  |            ^^

なるほど、わかりやすくなっている。できればこれを型を明示せずともやってもらいたいものだ。

この記事はめるぽんさんの運営するWandboxという大変便利な本物のWebサイトを活用して書いた。今年もめるぽんさんに野菜をスポンサーしにいかなければならない。