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サイトを活用して書いた。今年もめるぽんさんに野菜をスポンサーしにいかなければならない。