Return 在 do 块中的情况

Return case of in a do block

所以我是 Haskell 的新手,可能会问一个非常愚蠢的问题。 我尝试将以下代码获取到 运行:

asdf :: Maybe a -> Maybe a
asdf k = do
    return $ case k of 
                Nothing -> Nothing
                Just x -> Just x

我知道这基本上是 fmap 并且仅使用 return 是没有意义的。我真正想做的是以下

vote :: PostId -> Bool -> Maybe (Int, Int)
vote id v = do
  p <- runSQL $ P.get id
  return $ case p of
    Nothing -> Nothing
    Just p -> do
                let uv = postUpvotes p
                let dv = postDownvotes p
                let nuv = if v then uv + 1 else uv
                let ndv = if not v then dv + 1 else dv
                runSQL $ update id [PostUpvotes =. nuv, PostDownvotes =. ndv]
                (nuv, ndv)

我想这有不止一个问题。我的第一个例子到底有什么问题?第二个问题的方法是完全错误的还是怎么回事?你会怎么写?

您的第一个代码示例不需要 return.

你知道return在Haskell中的意思了吗?它不像任何其他编程语言中的 return。在大多数语言中,它是一个控制流语句,return 控制带有附加值的调用函数。在 Haskell 中,它只是一个具有误导性名称的函数。

在这种情况下你应该说:

asdf k =
    case k of 
            Nothing -> Nothing
            Just x -> Just x

在 Haskell do 中,符号与 monads 一起使用,如 IO。令人困惑的是 Maybe 实际上也是一个 monad。事实上,它是最简单的 monad。只有当你想要在某种上下文中 运行 的菊花链函数时,你才需要使用 monad,而这里不是这种情况。

继续你的第二个例子,问题是 runSQL 必须与外界互动,所以几乎可以肯定 return 是某种类型的 IO a a。在这种情况下,您在外部世界的上下文中执行菊花链功能,因此使用 monad 是必要的。

return 是一个函数,在本例中,其类型为

return :: a -> IO a

它真的应该被称为 pure 因为它采用纯值并将其包装在单子上下文中。 (当你进入 Applicative Functors 时,你会发现 return 的一个版本叫做 pure)。

关于 monad 的一个规则是,一旦进入 monadic 上下文,就无法退出;您可以在上下文中对值进行纯计算,但结果保留在单子上下文中。该上下文由 "IO" 之类的类型表示。所以在这种情况下你的函数类型应该是

vote :: PostId -> Bool -> IO (Maybe (Int, Int))

但是,如果您尝试这样做,您会发现它仍然不起作用。 case 的第一个分支很好,因为它 return 是 Nothing。但是第二个分支有一个类型错误,因为它在一个 IO 动作之后试图 return (nuv, nvd)。您已尝试分解 return,但实际上您需要在第一个分支中 return Nothing,然后在第二个分支末尾 return $ Just (nuv, nvd)

顺便说一句,您不需要为每个值都创建一个新的 let。你可以说

let
   foo = 1
   bar = 2
return (foo, bar)