【问题标题】:Return case of in a do block在 do 块中返回 case
【发布时间】:2017-07-31 16:35:28
【问题描述】:

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

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

我知道这基本上是fmap,并且只做返回是没有意义的。我真正想做的是以下

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)

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

【问题讨论】:

  • runSQL 的类型是什么?我认为它不是纯的,所以你不能在返回 Maybe (Int, Int) 的函数中运行它。
  • 来自本教程:spock.li/tutorials/rest-api
  • 除非在 IO monad 中返回一个值,否则不能执行 IO,可能类似于 IO (Maybe (Int, Int))

标签: haskell functional-programming monads


【解决方案1】:

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

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

在这种情况下你应该说:

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

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

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

return 是一个函数,在这种情况下,具有类型

return :: a -> IO a

它确实应该被称为pure,因为它接受一个纯值并将其包装在单子上下文中。 (当您访问 Applicative Functors 时,您会发现 return 的一个版本,称为 pure)。

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

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

但是,如果您尝试一下,您会发现它仍然无法正常工作。 case 的第一个分支很好,因为它返回 Nothing。但是第二个分支有一个类型错误,因为它试图在 IO 操作后返回 (nuv, nvd)。您已尝试将return 分解,但实际上您需要在第一个分支中添加return Nothing,然后在第二个分支末尾添加return $ Just (nuv, nvd)

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

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

【讨论】:

  • 我不相信 Maybe 是最简单的单子。可以说 Reader 也同样简单,但即使您不同意,Identity 也显然更简单。
  • 另外,vote 函数需要比你建议的更多的修复,因为它试图在第二个分支中执行一些 IO。它必须看起来更像case p of {Nothing -&gt; return Nothing; Just p -&gt; do ... return (nuv, ndv)},而不是return $ case ...
猜你喜欢
  • 2014-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-20
  • 2015-10-26
  • 1970-01-01
相关资源
最近更新 更多