我看过关于缩进的问题,但没有帮助。我的缩进看起来也正确,但根据编译器的说法并非如此。 什么是正确的缩进,规则是什么?
readFile filename = do
inputFile <- openFile filename ReadMode
readLines inputFile
hClose inputFile
readLines inputFile =
do endof <- hIsEOF inputFile
| endof = return()
| otherwise = do
inpStr <- hGetLine inputFile
print inpStr
readLines inputFile
使用所有空格,不使用制表符。 错误: "输入'|'上的解析错误 |endof = return() ">
你可以为此重组你的代码,比如
readLines :: Handle -> IO ()
readLines inputFile = g =<< hIsEOF inputFile
where -- hIsEOF :: Handle -> IO Bool
g endof
| endof = return ()
| otherwise = do
inpStr <- hGetLine inputFile
print inpStr
readLines inputFile
守卫| ...
属于函数定义或大小写表达式。他们不能自己出现在do
块中。
g =<< hIsEOF inputFile
是一种较短的写作方式
readLines inputFile = do { endof <- hIsEOF inputFile
; g endof
}
where
g endof
| endof = .....
但更简单的选择是首先在do
块中使用if ... then ... else ...
:
readLines inputFile =
do { endof <- hIsEOF inputFile
; if endof
then return()
else do { inpStr <- hGetLine inputFile
; print inpStr
; readLines inputFile
}}
另一个是使用LambdaCase来内联g
定义:
readLines :: Handle -> IO ()
readLines inputFile = hIsEOF inputFile >>=
( case { True -> return ()
; _ -> do
inpStr <- hGetLine inputFile
print inpStr
readLines inputFile })
案例子句可以有守卫(尽管这里我们不需要它们)。
正如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
表达式。至于缩进规则,你的代码是正确缩进的,但你不需要像你使用的那么多的空格:基本规则是:
某些关键字(如
do
、let
、where
和of
开始布局块
在这些块中,所有内容都必须缩进到块中第一行的第一列之后
如果表达式跨多行换行,则第一行后面的行必须缩进
因此,始终有效的经验法则是在每个此类关键字之后简单地添加一个换行符并缩进一定数量的空格(例如 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
最后,为了方便起见,可以使用Control.Monad
中的unless
或when
unless x y
或when (not x) y
编写if x then return () else y
:
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