正如 Will Ness 在他的回答中解释的那样,缩进不是您的问题 - 问题是您试图在 do 块中的语句之后使用防护 (| …),但防护只能永远出现在 (1) 函数方程中的模式和实体之间:
function param1 param2
| guard1 = body1
| guard2 = body2
…
和 (2) case 表达式:
case expr of
pattern1
| guard1 -> body1
| guard2 -> body2
pattern2
| guard3 -> body3
…
所以你可能想要一个if 表达式。至于缩进规则,你的代码缩进是正确的,但你不需要像你使用的那样多的空格:基本规则是:
因此,一个始终有效的经验法则是在每个这样的关键字之后简单地添加一个换行符和缩进一些空格(例如 2 或 4):
readFile filename = do -- newline+indent to begin block
inputFile <- openFile filename ReadMode
readLines inputFile
hClose inputFile
readLines inputFile = do -- newline+indent to begin block
endof <- hIsEOF inputFile
if endof -- indent to continue if expression
then return ()
else do -- newline+indent to begin block
inpStr <- hGetLine inputFile
print inpStr
readLines inputFile
另一种样式是在布局关键字的同一行开始一个块;那么所有内容都需要与该行具有相同的对齐方式:
readFile filename = do inputFile <- openFile filename ReadMode
readLines inputFile
hClose inputFile
readLines inputFile = do endof <- hIsEOF inputFile
if endof
then return ()
else do inpStr <- hGetLine inputFile
print inpStr
readLines inputFile
我更喜欢避免这种样式,因为它会导致缩进很深,这也取决于它之前代码的宽度。
这两种样式都与以下带有显式分隔符的代码脱糖,您也可以自己编写这些代码以更好地了解布局的工作原理:
readFile filename = do {
inputFile <- openFile filename ReadMode;
readLines inputFile;
hClose inputFile;
};
readLines inputFile = do {
endof <- hIsEOF inputFile;
if endof
then return ()
else do {
inpStr <- hGetLine inputFile;
print inpStr;
readLines inputFile;
};
};
经常让人绊倒的一件事是let 还引入了一个布局块,用于在同一个块中定义多个绑定。所以如果你写了一个let语句或者let…in…表达式的定义很长,那么你需要把它写成对齐:
let example1 = some very long definition
that we need to wrap across lines
example2 = another binding for illustration
-- ^ everything must be aligned past this column
in example1 . example2
或者使用与其他所有内容相同的换行符+缩进样式:
let
example1 = some very long definition
that we need to wrap across lines
example2 = another binding for illustration
in example1 . example2
最后,为方便起见,if x then return () else y 可以写成 unless x y 或 when (not x) y 使用 unless 或 when from Control.Monad:
import Control.Monad (unless)
…
endof <- hIsEOF inputFile
unless endof $ do
inpStr <- hGetLine inputFile
print inpStr
readLines inputFile
此外,您可能会在启用 BlockArguments 扩展的代码中看到 do(以及其他块关键字,如 case 和 \)之前省略了 $:
{-# LANGUAGE BlockArguments #-}
import Control.Monad (unless)
…
endof <- hIsEOF inputFile
unless endof do -- no need for $
inpStr <- hGetLine inputFile
print inpStr
readLines inputFile