我正在尝试了解do
表示法的规则。
这是一些进行类型检查的代码:
fff :: Maybe Int
fff = do
_ <- pure $ Just 100
(+10)
<$> Just 50
基本上是fff = (+10) <$> Just 50
。我认为上述内容可以not类型检查-因为肯定每一行都应该在Maybe
的上下文中,而(+10)
不在。
为什么要进行上述类型检查?这是上面的简单示例:
fff :: Int -> Maybe Int
fff i = do
(+10)
<$> Just i
为什么上述被认为是有效的语法?那不是“去糖”到:
fff i = ((+10) >>= (\i -> fmap (Just i))) i
这确实在ghci中给出了类型检查错误。
这里是一个示例,它按照与上面类似的缩进进行not类型检查:
x :: Maybe Int
x = do
_ <- Just 1
undefined
<$> Just 5
(感谢上面示例中的FP闲聊中的@cvlad)
这是一个奇怪的互动。
我开始将测试用例简化为此,可以正常运行。
> x = do succ ; <$> Just 1
> x
Just 2
通过比较,这不会解析:
> y = do { succ ; <$> Just 1 }
error: parse error
但是,此解析:
> z = do { succ } <$> Just 1
> z
Just 2
所以,这就是我所想的。由于令牌<$>
永远无法启动表达式,因此解析可能会失败。本质上,do
解析器规则是最大限制规则:失败时,添加隐式}
,然后重试。
因此,以上x
被解析为z
。由于succ
是单声道值(OP的问题中的行(+10)
),因此它可以出现在do
内部。这使类型检查成功。
在语法类别中也插入一个大括号 包含布局列表结尾;也就是说,如果一个非法的淫秽是 在大括号合法的地方遇到了 插入大括号。
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
是不正确的翻译,并且按照@chi答案中引用的规则来界定do
块的范围。
实际上是将其类型推断为
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
类型,我们不能在任何内容上映射that。
总是添加所有显式分隔符(除了练习良好的缩进样式外),以避免这种奇怪的语法脆弱性。
您的新示例是
x :: Maybe Int
x = do -- { this is
_ <- Just 1 -- ; how it is
undefined -- } parsed
<$> Just 5
代码已更改,但答案是相同的。 do
块之前 <$>
是Maybe t
(由于[C0]),我们不能fmap that。
再次,使最后一行缩进更多,它将编译,因为Just 1
现在将被解析为一个表达式。