使用防护措施更正缩进规则

时间:2019-04-15 11:21:20

标签: haskell syntax-error indentation do-notation guard-clause

我查看了有关缩进的问题,这些问题无济于事。我的缩进看起来也正确,但是根据编译器,它不是。 正确的缩进是什么,规则是什么?

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()“

2 个答案:

答案 0 :(得分:4)

您可以为此重组代码,例如

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 = .....

但是更简单的选择是首先在if ... then ... else ...块中使用do

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 })

并且案例子句可以有警卫(尽管这里我们不需要警卫)。

答案 1 :(得分:1)

正如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表达式。至于缩进规则,您的代码已正确缩进,但您并不需要使用的空白:基本规则是:

  • 某些关键词,例如doletwhereof开始布局图块

  • 在这些块中,所有内容都必须缩进到该块第一行的第一列之外

  • 如果表达式包含多行,则第一行之后的行必须缩进

因此,始终有效的经验法则是在每个此类关键字之后简单地添加换行符并缩进一些空格(例如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

另一种样式是在与layout关键字相同的行上开始一个块;那么一切都需要与该行具有相同的对齐方式:

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语句或letin…表达式,则需要将其写成对齐:

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 ywhen (not x) yunlesswhen写入Control.Monad

import Control.Monad (unless)

…
  endof <- hIsEOF inputFile
  unless endof $ do
    inpStr <- hGetLine inputFile
    print inpStr
    readLines inputFile

此外,您可能会在启用$扩展名的代码中看到do之前省略了case(以及其他\BlockArguments之类的块关键字) :

{-# LANGUAGE BlockArguments #-}
import Control.Monad (unless)

…
  endof <- hIsEOF inputFile
  unless endof do -- no need for $
    inpStr <- hGetLine inputFile
    print inpStr
    readLines inputFile