我正在努力理解do
表示法的规则。
以下是一些进行类型检查的代码:
fff :: Maybe Int
fff = do
_ <- pure $ Just 100
(+10)
<$> Just 50
基本上是CCD_ 2。我认为上面的内容可以而不是类型检查,因为肯定每一行都应该在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 slack聊天中的@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
内部。这将使类型检查成功。
引用Haskell报告2.7
只要语法类别包含布局列表结束;也就是说,如果一个非法的词位是在右大括号合法的情况下遇到插入支架。
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
,因为 我在TIO上收到了完全相同的错误消息<$> ...
本身是无效的表达式inputTest2 :: String -> IO (Either [String] (Int, Int))
inputTest2 fi = do {
allErrors2 <- undefined :: IO [String] ;
undefined }
<$> ((liftM2 ) (,) <$> undefined <*> undefined) fi
String
而不是您的类型(。
自从第一个undefined :: IO [String]
以来,整个do块都有一些fff = (+10) <$> Just 50
0类型,我们不能在任何东西上形成。
总是添加所有显式分隔符(除了练习良好的缩进样式(,以避免这种奇怪的语法脆性。
您的新示例是
x :: Maybe Int
x = do -- { this is
_ <- Just 1 -- ; how it is
undefined -- } parsed
<$> Just 5
代码变了,但答案是一样的。在<$>
之前的do
块是Maybe t
(因为Just 1
(,并且我们不能形成的。
同样,再缩进最后一行,它就会编译,因为undefined <$> Just 5
现在将被解析为一个表达式。