haskell - 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)
解决方案
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 t
Just 1
再次缩进最后一行,它会编译,因为undefined <$> Just 5
现在将被解析为一个表达式。
推荐阅读
- node.js - 在谷歌日历 API 中访问“忙碌”数组
- angular - Angular ngSwitchCase - 具有相同值的开关
- ejabberd - 如何将 ejabberd 与 phpmyadmin 连接以将消息存储到数据库中?
- python - 有没有办法为我的挑战减少这个 Python 代码的内存?
- r - 如何在 R 中完成行名?
- javascript - 将对象键的值推送到数组中
- c - 为什么添加临界区会导致分段错误?
- python - ValueError:层顺序需要 1 个输入,但它接收到 250 个输入张量
- android - 试图在android中将数据从一个活动传输到另一个活动,但它在另一个活动上显示空值
- python - 使用 Python 生成动态 sql 查询