TL; DR
こういうコードを書いて、
f y =
let y = case x of
0 -> 0
_ -> 1
in y
parse error on input ‘->’
というエラーメッセージが出たら、
f x =
let y = case x of
0 -> 0
_ -> 1
in y
こう書き換えればコンパイルが通ります。
再現コード
このコードが最小再現コードになります。調査環境は IDE One(ghc 8.0.1)です。
f x =
let y = case x of
0 -> 0
_ -> 1
in y
注: 言うまでもないかもしれませんが、このコードは
f 0 = 0
f _ = 1
と書くべきです。これはあくまでコンパイルエラーを起こすためのコードであり、実際にはここまで単純なコードではない(このような書き換えができない)ものと考えてください。
なぜエラーになるのか
まず前提として、Haskell の let 式の書式はこうです。
let x = 0
y = 1
in x + y
{-
let 変数名1 = 式1
変数名2 = 式2
in 式0
-}
したがって、エラーになるコードをコンパイラが見ると
let y = case x of
0 -> 0
_ -> 1
in y
let の2行目では 変数名 = 式
の形が来ると予想しているために、エラーになります。0
は変数名として不正なのでそこでエラーになって欲しいところですが、実際にエラーメッセージが表示されるのは ->
の部分になります。
つまり、コンパイラにとっては 0 -> 0
や _ -> 1
は case ではなく let に属しているように見えているということです。人間にとっては 0 -> 0
が case の一部なのは自明ですが、以下のようなコードであればどうでしょうか。
let y = case x of
z -> 0
w = 2
in y + w
case がどこまで続いているのか、let で束縛されるのはどれか、すぐに判別できたでしょうか。コンパイラとしては、z
や w
を見ただけではこれが let の一部なのか case の一部なのかわかりません。決定しようとすれば次のトークンである =
か ->
を見る必要があります。しかしそれではパースが大変になるので、Haskell は文法を簡単にする道を選んだのでしょう。
解決方法
0 -> 0
が let と case のどちらに属するかわからないのですから、インデントにより明確化すれば OK です。
let y = case x of
0 -> 0
_ -> 1
in y
-- ^ let
-- ^ case
let と case で合わせて 2段階インデントを深くすればいい訳です。
なお、case の範囲さえわかればいいので、次のような書き方でも問題ありません。
let y = case x of {
0 -> 0;
_ -> 1;
}
in y
インデントルールの闇そんなことなかった
追記: 以下の記述は誤りです。詳細はコメント欄を参照してください。
以下のコードで、f
はコンパイルでき g
はコンパイルできません(in IDE One Haskell ghc 8.0.1)。なぜだかわかりますか?
-- OK
f x =
let y = case x of -- 2 spaces
0 -> 0 -- 2 + 4 + 1 spaces
_ -> 1
in y
-- OK
f x =
let y = case x of -- 4 spaces
0 -> 0 -- 4 + 4 + 1 spaces
_ -> 1
in y
-- OK
f x =
let y = case x of -- 1 tab (8 spaces)
0 -> 0 -- 2 tabs (8 + 4 + 4 spaces)
_ -> 1
in y
-- Error
g x =
let y = case x of -- 4 spaces
0 -> 0 -- 4 + 4 spaces
_ -> 1
in y
-- Error
g x =
let y = case x of -- 2 spaces
0 -> 0 -- 2 + 2 + 2 spaces
_ -> 1
in y
どうやら Haskell は、インデントは 4スペースでするもの、という前提を持っているようです。let
の前のスペースの数に関わらず、let
の桁位置から4+1スペース離れれば let とは別のものとして扱っています。とにかく、余計な混乱を防ぐには、
Haskell のインデントはスペース 4 つを使え
ということのようです。ところで、その割に2スペースでインデントされたコードをよく見るのはなぜなんでしょうか。この挙動を制御するようなコンパイルオプションがあるとか?