对 Haskell 的布局工具如何处理此代码感到困惑

Perplexed by how this code is processed by Haskell's Layout facility

在浏览https://wiki.haskell.org/IO_inside时,我遇到了以下评论和代码...

"Moreover, Haskell layout rules allow us to use the following layout:

main = do a <- readLn
          if (a>=0) then return ()
            else do
          print "a is negative"
          ...

that may be useful for escaping from the middle of a longish 'do' statement."

以后我会用符号C*来指代上面的代码

我推测C*的本意是读入一个数,然后:
(i) 如果它是非负的,什么也不做。
(ii) 如果为负数,则显示输出表明它为负数。

我最初的反应是认为 C* 要么无法正确解析,要么无法按预期运行。

我认为 Layout 会在第二个 'do' 之后插入一组空的大括号和一个分号,因为 lexeme 'print' 的缩进不超过当前已建立的布局上下文的缩进级别通过 "a <- readLn" 中的 'a'。

也就是说,我对Layout生成的布局不敏感代码(以下简称C')的预测是这样的:

main = do {
          a <- readLn;
          if (a>=0) then return ()
            else do {};
          print "a is negative"
          ...
          }

我认为情况会是这样,这是基于 Haskell 2010 年语言报告第 1 部分第 2.7 节 ('Lexical Structure' : 'Layout') 中包含的以下句子(https://www.haskell.org/onlinereport/haskell2010/haskellpa1.html):

"If the indentation of the non-brace lexeme immediately following a where, let, do or of is less than or equal to the current indentation level, then instead of starting a layout, an empty list “{}” is inserted, and layout processing occurs for the current level (i.e. insert a semicolon or close brace)."

Haskell 的第 1 部分(URL 以上给出)的第 10.3 节('Syntax Reference' : 'Layout')给出了布局规则的更详细说明2010年语言报告。

阅读这个更详细的说明后,我感到很放心,因为我对布局(即 C')生成的布局不敏感代码的预测是正确的。

然而,令我惊讶的是,当我在 GHCi 中尝试上面规定的原始代码(即 C*)时,它有效(正确解析并按预期运行)。

问题...

  1. 我上面引用的那句话(来自2.7节)准确吗?

  2. 上述Layout规则的详细说明(来自10.3节)是否准确?

  3. 我用来预测由 Layout 为原始代码 C* 生成的布局不敏感代码(即 C')的推理有哪些缺陷?

  4. 对于上面规定的原始代码(即对于C*),Layout产生的layout-insensitive代码是什么,解释它的规则/原则是什么?

  5. 总的来说,有没有办法可以查看Layout生成的布局不敏感代码?如果是,它是什么(请详细说明/解释适合 Haskell 新手的水平的技术,比如我)?

这是 known and documented deviation 默认的 Haskell 标准或 Haskell 98 模式的 GHC。

GHC 有一个名为 NondecreasingIndentation 的语言扩展,可用于触发此行为。如果启用,do 关键字会引入一个新块,即使下一个标记以与周围块相同的缩进级别开始也是如此。

如果您不想这样,请说 -XNoNondecreasingIndentation-XHaskell2010(或相应地使用语言编译指示)。

您可以通过将 -ddump-parsed 标志传递给 GHC 来查看 GHC 解析的代码的打印版本。这只会部分删除布局(它对 do 块这样做,但例如不是 let),但可能仍会提供线索。