考虑以下代码:
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
Run Code Online (Sandbox Code Playgroud)
在 中foo,我已手动内联Control.Monad.guard (x < 5),如评论中所述。
为什么foo编译,即使pure ()在代码中?如何[()]通过类型检查?它是 do 语法的特例吗?如果是,是否在任何地方进行了描述?
在 中foo2,我尝试在foo没有 do 语法的情况下“脱糖” 。请注意,不能有 any pure (),因为它没有通过类型检查。
如果这很重要,我正在使用 ghc-8.8.4。
您的手动脱糖有几个错误。仅使用的一种尝试>>=是:
foo2 :: [Int]
foo2 =
[1..10] >>= \x ->
(if x < 5 then pure () else []) >>= \_ ->
pure x
Run Code Online (Sandbox Code Playgroud)
首先,括号很重要:您绑定的是整个if表达式的结果,而不是在其else分支内执行绑定。其次,您不能只引入一个新变量y,然后在pure结果中使用它。脱糖在您的源代码中保留了相同的表达式,只是稍微移动了它们。所以,pure x一定要脱糖pure x。
希望你能明白为什么会这样:类型()无关紧要,因为没有人会查看它的值,而结果 ,pure x无论如何都具有正确的类型。
但实际上 GHC 并没有产生这样的代码:x >>= \_ -> y相当于x >> y,这就是 do-block 中的语句所用的,它不将其结果绑定到变量。所以你真的得到
foo2 :: [Int]
foo2 =
[1..10] >>= \x ->
(if x < 5 then pure () else []) >> pure x
Run Code Online (Sandbox Code Playgroud)
如果您愿意,您可以使用 Functor 中稍微高级的运算符来实现相同的结果。让我们取消内联guard,并使用(<$) :: Functor f => a -> f b -> f a而不是一元操作。x <$ y是一样的y >> pure x:
foo2 :: [Int]
foo2 =
[1..10] >>= \x ->
x <$ guard (x < 5)
Run Code Online (Sandbox Code Playgroud)