为Monad解析do-notation

28

在学习 Haskell 的过程中,我意识到 do 表示法只是一种语法糖:

a = do x <- [3..4]
       [1..2]
       return (x, 42)

翻译成

a = [3..4] >>= (\x -> [1..2] >>= (\_ -> return (x, 42)))

我知道我可能会使用do-notation,但我想理解翻译中发生了什么。出于纯粹的教育目的,是否有一种方法让ghc / ghci为我提供相应的绑定语句,以便了解使用do-notation编写的相当复杂的monad的情况?

编辑。事实证明,在#haskell上的lambdabot可以做到这一点:

<Guest61347> @undo do x <- [3..4] ; [1..2] ; return (x, 42)
<lambdabot> [3 .. 4] >>= \ x -> [1 .. 2] >> return (x, 42)

这是撤销插件的源代码。


4
实际上,m >>= (\_ -> k) 等同于 m >> k - Matvey Aksenov
8
Lambdabot有一个“undo”插件,用于展开Do表示法。 - Dan Burton
1
我正在学习,感觉这是真正理解特定实例细节的关键 - 它们都是用 >>= 表示的。 - JonnyRaa
1个回答

23

你可以请求 GHC 的 desugarer 输出,不过这将会对很多其他语法进行反糖处理。

首先,我们将把你的代码放在一个名为 Foo.hs 的模块中:

module Foo where

a = do x <- [3..4]
       [1..2]
       return (x, 42)

接下来,我们将要求 GHC 编译它,并在解糖阶段后输出结果:

$ ghc -c Foo.hs -ddump-ds

输出可能看起来相当混乱,因为它是Haskell的一种变体,称为Core,用作GHC的中间语言。然而,一旦你熟悉它,它并不太难阅读。在其他一些定义的中间,我们发现了你的定义:

Foo.a :: [(GHC.Integer.Type.Integer, GHC.Integer.Type.Integer)]
LclIdX
[]
Foo.a =
  >>=_agg
    @ GHC.Integer.Type.Integer
    @ (GHC.Integer.Type.Integer, GHC.Integer.Type.Integer)
    (enumFromTo_ag7
       (GHC.Integer.smallInteger 3) (GHC.Integer.smallInteger 4))
    (\ (x_adf :: GHC.Integer.Type.Integer) ->
       >>_agn
         @ GHC.Integer.Type.Integer
         @ (GHC.Integer.Type.Integer, GHC.Integer.Type.Integer)
         (enumFromTo_ags
            (GHC.Integer.smallInteger 1) (GHC.Integer.smallInteger 2))
         (return_aki
            @ (GHC.Integer.Type.Integer, GHC.Integer.Type.Integer)
            (x_adf, GHC.Integer.smallInteger 42)))

Core 不太好看,但是当你使用 GHC 时能够阅读它会非常有用,因为在后期阶段可以读取转储信息,查看 GHC 如何优化代码。

如果我们删除重命名器添加的 _xyz 后缀,以及类型应用程序 @ Xyz 和对 GHC.Integer.smallInteger 的调用,并使操作符再次成为中缀形式,你就会得到像这样的东西:

Foo.a :: [(GHC.Integer.Type.Integer, GHC.Integer.Type.Integer)]
Foo.a = enumFromTo 3 4 >>= \x -> enumFromTo 1 2 >> return (x, 42)

4
这就引出一个问题……有没有什么东西可以自动删除_xyz后缀、类型应用、调用GHC.Integer.smallInteger并使操作符再次成为中缀形式? - hugomg
1
@missingno:据我所知,没有这样的库。但是,在Hackage上有可用于处理Haskell或Core的库,因此如果您非常需要,组合一些库应该不会太难。 - hammar
4
问题在于,当你写好能做到那个的代码时,你已不再需要它了。 - Paul Johnson
9
有一个标记可以抑制后缀,ghc -ddump-ds -dsuppress-uniques Foo.hs将从输出中删除它们。从 ghc-7.2 开始,还有一个 -ddump-to-file 标记,它将使 ghc 将输出转储到此处的 Foo.dump-ds 文件中。如果您想要多个转储,则非常有用,例如 ghc -ddump-ds -ddump-simpl -ddump-to-file Foo 将生成 Foo.dump-ds 和 Foo.dump.simpl 文件(顺便说一句,-ddump-simpl 也提供了一种去糖语法的方式,如果您不进行优化编译)。 - Daniel Fischer
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接