考虑以下代码:
foo :: [Int]
foo = do
x <- [1..10]
if x < 5 then pure () else [] -- Control.Monad.guard (x < 5)
pure x
foo2 :: [Int]
foo2 =
[1..10] >>= \x ->
if x < 5 then pure x else [] >>= \y ->
pure y
在 foo
, 我已经手动内联 Control.Monad.guard (x < 5)
,如评论中所述。为什么
foo
编译,即使有 pure ()
在代码中? [()]
如何通过类型检查?它是 do 语法的特例吗?如果是,是否在任何地方进行了描述?在
foo2
,我试图“脱糖”foo
没有 do 语法。注意不能有pure ()
,因为它没有通过类型检查。如果这很重要,我正在使用 ghc-8.8.4。
最佳答案
您的手动脱糖有几个错误。仅使用 >>=
的一次尝试将会:
foo2 :: [Int]
foo2 =
[1..10] >>= \x ->
(if x < 5 then pure () else []) >>= \_ ->
pure x
首先,括号很重要:您将绑定(bind)整个 if
的结果。表达式,不在其 else
内执行绑定(bind)分支。其次,不能只引入一个新变量y
然后在你的 pure
中使用它结果。脱糖会在源代码中保留相同的表达式,只是稍微移动它们。所以,pure x
必须脱糖到 pure x
.希望你能明白为什么会这样:
()
的类型没关系,因为没有人看过它的值,结果是pure x
, 无论如何都有正确的类型。但实际上 GHC 并不会产生完全这样的代码:
x >>= \_ -> y
相当于x >> y
, 这就是 do-block 中不将其结果绑定(bind)到变量的语句所使用的。所以你真的得到foo2 :: [Int]
foo2 =
[1..10] >>= \x ->
(if x < 5 then pure () else []) >> pure x
如果您愿意,您可以使用 Functor 中稍微花哨的运算符来获得相同的结果。让我们取消内联 guard
, 并使用 (<$) :: Functor f => a -> f b -> f a
而不是一元操作。 x <$ y
与 y >> pure x
相同:foo2 :: [Int]
foo2 =
[1..10] >>= \x ->
x <$ guard (x < 5)
关于Haskell list monad 和 return (),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64348649/