【问题标题】:Correct indentation rules using guards使用警卫纠正缩进规则
【发布时间】:2019-09-05 08:54:42
【问题描述】:

我查看了有关缩进的问题,但没有任何帮助。我的缩进看起来也正确,但根据编译器它不是。 什么是正确的缩进,规则是什么?

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

【问题讨论】:

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


    【解决方案1】:

    你可以为此重构你的代码,比如

    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 =&lt;&lt; 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 })
    

    并且 case 子句可以有保护(虽然这里我们不需要它们)。

    【讨论】:

    • 谢谢! “readLines :: Handle -> IO ()”的目的是什么?
    • 它是函数的类型签名。 readLines 是函数的名称,:: 表示“具有”A -&gt; B 表示“从 A 类型的值到 B 类型的值的函数”。
    • 谢谢!是否有必要声明签名或只是(非常)有用?
    • 后者。 :)
    【解决方案2】:

    正如 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
    

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

    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) y 使用 unlesswhen 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
    

    【讨论】:

      猜你喜欢
      • 2021-12-10
      • 1970-01-01
      • 2015-11-07
      • 2021-05-03
      • 1970-01-01
      • 2019-04-06
      • 1970-01-01
      • 2012-02-14
      • 2019-12-14
      相关资源
      最近更新 更多