【问题标题】:Let clause nested in if clause nested in do clause嵌套在 if 子句中的 let 子句嵌套在 do 子句中
【发布时间】:2015-10-31 22:09:20
【问题描述】:

我目前正在通过 Learn You a Haskell for Great Good 进行学习,并且我正在尝试修改第九章“输入和输出”中的一个代码 sn-ps 以正确处理错误:

main = do
    (command:args) <- getArgs
    let result = lookup command dispatch
    if result == Nothing
        then
            errorExit 
        else
            let (Just action) = result
    action args

在哪里

dispatch :: [(String, [String] -> IO ())]

是一个关联列表

errorExit :: IO ()

是一些打印错误信息的函数。

用 GHC 编译会给出错误信息

todo.hs:20:13: parse error in let binding: missing required 'in'

这(据我所知)似乎是说这里的“让”没有意识到它在“做”块中。

在第五行和第七行(分别在“then”和“else”之后)添加“do”,将错误消息更改为

todo.hs:20:13:
The last statement in a 'do' block must be an expression
  let (Just action) = result

todo.hs:21:5: Not in scope: `action'.

现在,虽然我同意第一条错误消息,但我也有一个变量已经超出范围?我仔细检查了我的对齐方式,似乎没有什么不合适的地方。

在 do 块中的 if 子句中分配变量的适当方法是什么?

【问题讨论】:

    标签: haskell


    【解决方案1】:

    我的建议是首先不要使用if,使用case。通过使用case,您可以一次性测试该值并将结果绑定到一个变量。像这样:

    main = do
        (command:args) <- getArgs
        case lookup command dispatch of
          Nothing -> errorExit
          Just action -> action args
    

    要更深入地讨论为什么我们应该更喜欢case 而不是if,请参阅boolean blindness

    【讨论】:

    • 这是一种更简洁的书写方式,谢谢。另外,我以前没有听说过布尔盲!你知道为什么我的版本不起作用吗?
    【解决方案2】:

    @svenningsson 提出了正确的解决方案。您原来失败的原因是因为 let 子句 只能出现在 do 块的顶层 - 它们是不考虑内部表达式的简单语法糖:

    do let x = 1
       y
    

    去糖到let 表达式

    let x = 1 in y
    

    唉,在do 块中,像if ... then ... else ... 这样的表达式 子句根本无法在do 块的其余部分中声明变量。

    至少有两种可能的方法来解决这个问题。

    1. 吸收do块的剩余部分表达式中:

      main = do
          (command:args) <- getArgs
          let result = lookup command dispatch
          if result == Nothing
              then
                  errorExit 
              else do
                  let (Just action) = result
                  action args
      

      (这本质上也是@svenningsson 在他更好的case 版本中使用的方法。)

      但是,如果需要将 do 表达式的其余部分复制到多个分支中,这可能会有点尴尬。

      (“秘密”技巧:GHC(与标准 Haskell 不同)实际上并不需要最终的内部 do 块比外部块缩进更多,如果缩进量开始变得烦人,这会有所帮助。)

    2. 将变量声明拉到表达式之外:

      main = do
          (command:args) <- getArgs
          let result = lookup command dispatch
          action <- if result == Nothing
              then
                  errorExit 
              else do
                  let (Just action') = result
                  return action'
          action args
      

      这里需要组成一个新的变量名,因为let 子句中的模式不仅仅是一个简单的变量。

    最后,action 在代码的最后一行总是超出范围,但 GHC 分几个阶段工作,如果它在解析阶段中止,它不会检查范围错误。 (由于某种原因,它会在解析之后进行The last statement in a 'do' block must be an expression 检查。)

    附录:在我理解@Sibi 的意思后,我发现result == Nothing 不起作用,所以即使有上述变通方法,你也不能使用if ... then ... else ...

    【讨论】:

      【解决方案3】:

      您收到错误是因为您尝试比较函数类型的值。当您执行检查if result == Nothing 时,它会尝试检查Nothingresult 的值是否相等,Maybe ([String] -&gt; IO ()) 的类型。

      因此,如果您希望它正确地进行类型检查,您必须为 -&gt; 定义 Eq 实例,这在您尝试比较两个函数是否相等时没有任何意义。

      您也可以使用fmap 来编写您的代码:

      main = do
          (command:args) <- getArgs
          let result = lookup command dispatch
          print $ fmap (const args) result
      

      【讨论】:

      • 嗯,我想你算错了result的类型,它是Maybe
      • @ØrjanJohansen 啊是的,修好了。
      • 哦,我也误解了你的意思。虽然我认为 OP 的原始代码没有进入类型检查阶段。另外,我不认为print $ fmap ... 会按照他们的意思行事。你至少需要像traverse_ 这样的东西。
      猜你喜欢
      • 1970-01-01
      • 2018-08-12
      • 2016-08-15
      • 2019-08-28
      • 2021-07-05
      • 2017-06-10
      • 2014-09-10
      • 2019-03-26
      相关资源
      最近更新 更多