Haskell中case表达式需单独换行写分号的语法疑问
Haskell中case表达式分号与where子句的解析问题
这是Haskell解析器的预期行为,核心原因在于where子句的作用域以及Haskell的语法解析规则——当使用花括号{}显式分隔case分支时,分号是分支的分隔符,但where子句属于其所在的整个case分支(即->右侧表达式的一部分),而非独立的分支项。
为什么单独一行的分号能正常编译?
当你把分号单独放在一行时,相当于明确告诉解析器:当前包含where子句的分支已经完全结束,下一行的内容是新的case分支。这种写法通过换行+分号的组合,清晰分隔了不同分支的边界,避免解析器把下一个分支误判为当前where子句的一部分。
不用单独分号的解决方案:使用Haskell布局规则
Haskell设计了布局规则(Layout Rule),允许用缩进代替花括号和分号来分隔代码块,这也是Haskell最常用的写法,能彻底避免这类分号问题。修改你的readsPrec实现如下:
instance Read BinaryNumber where -- idea: -- next item must be known to decide whether E or L constructor to use -- create function that will be called on the next element and returns the right constructor, -- with only one parameter, the current digit value, unset readsPrec _ input = case input of ('0':'0':xs) -> readsPrec 0 ('0':xs) ('0':'1':xs) -> readsPrec 0 ('1':xs) ('0':xs) -> [(next Zero, rest)] where (next, rest) = readAll xs ('1':xs) -> [(next One , rest)] where (next, rest) = readAll xs otherwise -> [] where readAll :: String -> (BinaryDigit -> BinaryNumber, String) readAll ('0':xs) = ((`E` (next Zero)), rest) where (next, rest) = readAll xs readAll ('1':xs) = ((`E` (next One )), rest) where (next, rest) = readAll xs readAll str = (L, str)
这里完全去掉了case的花括号和分号,靠缩进区分不同的case分支,每个分支的where子句通过更深的缩进明确属于当前分支,解析器能正确识别结构。
错误写法的原因分析
你尝试的几种写法都会触发解析错误,本质都是因为分号的位置导致解析器混淆了分支边界和where子句的范围:
- 第一种:
... where ...;—— 分号紧跟在where子句后,解析器会误认为下一个('1':xs)分支是当前where块内的代码,自然无法解析这个不符合where子句语法的内容。 - 第二种:给
readAll xs加括号不改变问题核心——错误根源是分号的位置,而非表达式本身的语法。 - 第三种:
where单独缩进但末尾加了分号——分号干扰了布局规则的解析,解析器依然会把下一个分支当成当前where块的一部分,导致语法错误。
修改后的完整可运行代码
data BinaryDigit = Zero | One data BinaryNumber = L BinaryDigit -- L: last digit | E BinaryDigit BinaryNumber -- E: intermediate digit instance Show BinaryNumber where show (L Zero) = "0" show (L One ) = "1" show (E digit next) = show (L digit) ++ show next instance Read BinaryNumber where -- idea: -- next item must be known to decide whether E or L constructor to use -- create function that will be called on the next element and returns the right constructor, -- with only one parameter, the current digit value, unset readsPrec _ input = case input of ('0':'0':xs) -> readsPrec 0 ('0':xs) ('0':'1':xs) -> readsPrec 0 ('1':xs) ('0':xs) -> [(next Zero, rest)] where (next, rest) = readAll xs ('1':xs) -> [(next One , rest)] where (next, rest) = readAll xs otherwise -> [] where readAll :: String -> (BinaryDigit -> BinaryNumber, String) readAll ('0':xs) = ((`E` (next Zero)), rest) where (next, rest) = readAll xs readAll ('1':xs) = ((`E` (next One )), rest) where (next, rest) = readAll xs readAll str = (L, str) main = putStrLn $ show (read "110" :: BinaryNumber)
内容的提问来源于stack exchange,提问作者Bence
相关产品推荐
相关产品推荐

