正如 Will Ness 在他的回答中解释的那样,缩进不是你的问题 - 问题是你试图使用防护(| …</code>) after a statement in a do
block, but guards can only ever appear between patterns and bodies in (1) function equations:
function param1 param2
| guard1 = body1
| guard2 = body2
…
And (2) case
expressions:
case expr of
pattern1
| guard1 -> body1
| guard2 -> body2
pattern2
| guard3 -> body3
…
So you probably want an if
expression instead. As for the rules of indentation, your code is indented correctly, but you don’t need as much whitespace as you’re using: the basic rules are:
Certain keywords such as do
, let
, where
, and of
begin layout blocks
Within those blocks, everything must be indented past the first column of the first line in the block
If an expression wraps across multiple lines, the lines following the first must be indented
So a rule of thumb that always works is to simply add a newline and indent by some number of spaces (e.g. 2 or 4) after every such keyword:
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
The alternative style is to start a block on the same line as the layout keyword; then everything needs to have the same alignment as that line:
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
I prefer to avoid this style because it leads to deep indentation, which also depends on the width of the code before it.
Both of these styles are desugared to the following code with explicit delimiters, which you can also write yourself to get a better feel for how layout works:
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;
};
};
One thing that often trips people up is that let
also introduces a layout block for defining multiple bindings in the same block. So if you write a let
statement or let
…<code>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
or when
from Control.Monad
:
import Control.Monad (unless)
…
endof <- hIsEOF inputFile
unless endof $ do
inpStr <- hGetLine inputFile
print inpStr
readLines inputFile
此外,您可能会在启用扩展的代码中看到$
省略的 before do
(以及其他块关键字,如case
and ):\
BlockArguments
{-# LANGUAGE BlockArguments #-}
import Control.Monad (unless)
…
endof <- hIsEOF inputFile
unless endof do -- no need for $
inpStr <- hGetLine inputFile
print inpStr
readLines inputFile