首页 > 解决方案 > Haskell do notation 边缘情况无法进行类型检查

问题描述

我试图理解do符号规则。

这是一些类型检查的代码:

fff :: Maybe Int
fff = do
    _ <- pure $ Just 100
    (+10)
    <$> Just 50

这基本上是fff = (+10) <$> Just 50. 我会假设上面的内容不会检查类型 - 因为肯定每一行都应该在Maybe其中(+10)不是的上下文中。

为什么上面的类型检查?这是上面的一个更简单的例子:

fff :: Int -> Maybe Int
fff i = do
    (+10)
    <$> Just i

为什么上述被认为是有效的语法?这不是“脱糖”吗:

fff i = ((+10) >>= (\i -> fmap (Just i))) i

这确实在 ghci 中给出了类型检查错误。


这是一个在与上面类似的缩进之后不进行类型检查的示例:

x :: Maybe Int
x = do
  _ <- Just 1
  undefined
  <$> Just 5

(感谢上述示例中来自 FP 闲聊的@cvlad)

标签: haskellsyntaxdo-notation

解决方案


fff :: Int -> Maybe Int
fff i = do
    (+10)
    <$> Just i

为什么上述被认为是有效的语法?

因为它被解析为

fff i = do {        -- do { A } is just
    (+10) }         --      A
    <$> Just i

这相当于

fff i =
    (+10) 
    <$> Just i

因为<$> Just i它本身就是一个无效的表达式(fff i = ((+10) >>= (\i -> fmap (Just i))) i不正确的翻译也是如此),并且根据@chido的答案中引用的规则来界定块的范围。

事实上,它的类型被推断为

fff :: Num b => b -> Maybe b

如果您<$>在最后一行之前添加一个空格,则第二个示例有效。没有空格,它再次被解析为

inputTest :: FormInput -> IO (Either [String] (Int, Int))
inputTest fi = do {
    allErrors' <- undefined :: IO [String]
    undefined }
    <$> ((liftM2 ) (,) <$> undefined <*> undefined) fi

因为<$> ...它本身是无效的表达。确实,当我添加显式分隔符时,

inputTest2 :: String -> IO (Either [String] (Int, Int))
inputTest2 fi = do {
    allErrors2 <- undefined :: IO [String] ;
    undefined  }
    <$> ((liftM2 ) (,) <$> undefined <*> undefined) fi

我在 TIO 上得到完全相同的错误消息(必须使用String而不是你的类型)。

从第一个 开始undefined :: IO [String],整个 do 块都有某种IO t类型,我们不能将它映射任何东西上。

始终添加所有显式分隔符(除了练习良好的缩进样式),以避免这种奇怪的语法脆弱性。


你的新例子是

x :: Maybe Int
x = do          -- {     this is
  _ <- Just 1   -- ;       how it is
  undefined     -- }         parsed
  <$> Just 5

代码改变了,但答案是一样的。之前do块是(因为),我们不能fmap<$>Maybe tJust 1

再次缩进最后一行,它会编译,因为undefined <$> Just 5现在将被解析为一个表达式。


推荐阅读