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スペースでインデントされたコードをよく見るのはなぜなんでしょうか。この挙動を制御するようなコンパイルオプションがあるとか?