2

请原谅我的无知,但是,有没有办法调用Handler ()从只返回的方法返回的方法IO ()

例如考虑这两种方法

onReceiveMessage :: IO ()
onReceiveMessage = do
  _ <- putStrLn "Received Message" 
  _ <- doSomething
  return ()

doSomething :: Handler ()
doSomething = return ()

这似乎无法编译。我试图以几种不同的方式编译它,但都是一致的。我相信这一定是可能的,但只是不知道如何。

任何想法?


更新

扩展上一个示例,假设我有另一个函数,它接收上一个函数的值并返回 IO ()。那也行不通。

onReceiveMessage :: IO ()
onReceiveMessage = 
    doSomething >>= doSomethingElse

doSomething :: Handler MessageStatus
doSomething = return MessageStatus.Success

doSomethingElse :: MessageStatus -> IO ()
doSomethingElse _ = return ()

这似乎也不起作用。可以使用 liftIO 函数从 Handler 调用 IO 操作,例如下面的函数可以编译并正常工作。它从返回 Handler MessageStatus 的函数调用 IO () 操作。这是使用 liftIO 函数实现的。

doSomething' :: Handler MessageStatus
doSomething' = (liftIO $ putStrLn "hello world") >> return MessageStatus.Success

我们有类似从 IO 调用 Handler 操作的东西吗?


更新 2

提供更多上下文并解释我是如何解决问题的。

我试图在 Yesod 应用程序中 使用amqp包来收听 RabbitMQ 。

  • 当我收到一条消息时,会调用一个回调。
  • 回调需要有签名(Message, Envelope) -> IO ()
  • 从回调中我需要执行一些 SQL。
  • 现在我不想编写代码来解析配置文件并管理我自己的连接池。
  • 因此,我想将我的代码与 Yesod 提供的名为runDB.
  • 但是由于它的返回值被包装在Handler我无法从消息回调中调用它。

我最终做的是

  • 获取App( Foundation) 对象,同时将其构建在 中Application.hs并将其传递给我的代码。
  • 然后为消息回调创建一个柯里化函数
  • 在 curried 函数中,我保留了 Yesod 已经构建的配置对象,同时构建了基础
  • 在我弄清楚这一点之后很轻松,使用配置对象我可以从 settings.yml 中读取我的所有设置,甚至保持一个并行连接池。
  • 此连接池将用于从消息回调中触发我的所有查询。
  • 不仅如此,而且因为我使用了这种方法,我可以免费获得日志记录,并且无需编写任何代码就可以在控制台上查看所有查询。

总的来说,我觉得我可能有复杂的事情,但目前我不知道有什么更好的方法。如果有人有更好的主意,我很想听听。

4

2 回答 2

1

在最近的版本中,您可以使用Yesod.Core.Handler.handlerToIO它来回到您舒适的 Handler 堆栈。

但我建议将消息​​传递和处理解耦:

getOneMsg :: Channel -> Text -> Handler (Message, Envelope)
getOneMsg chan queue = liftIO $ do
    mbox <- newEmptyMVar
    tag <- consumeMsgs chan queue NoAck (putMVar mbox)
    reply <- takeMVar mbox
    cancelConsumer chan tag
    return reply

myHandler = do
    ... regular handler code ...
    (msg, env) <- getOneMsg chan queue
    ... regular handler code ...
于 2014-02-21T14:01:19.827 回答
1

首先,你的缩进看起来不对劲。Haskell 中的空白很重要。但更重要的是,单个 monadic 动作中的每个语句都必须在同一个 monad 中:

onReceiveMessage :: IO ()
onReceiveMessage = do
    putStrLn "Received Message" :: IO ()
    doSomething :: Handler ()   -- Illegal! Must be type IO ()
    return () :: IO ()

doSomething :: Handler ()
doSomething = return ()

所以不,你不能从一个IO动作中返回一个处理程序。


更新

Handler类型实际上是更复杂类型的别名,您可以将其视为“包装” IO monad。它允许您做的是将 IO 动作“提升”到Handlermonad 中,但这只是一种方式。为了将Handler动作转换为IO动作,库提供的函数通常类似于runHandler :: Handler [-> other args] -> IO (). 我对 Yesod 不是特别熟悉,但这种模式在许多库中都很相似。该函数用于将整个Handler动作转换为IO动作,通常保留用于运行服务器本身。

复杂的答案是Handler类型是所谓的单子变压器。一开始它们可能很难学习,但你可以把它想象成一个包含另一个 monad 的 monad。Monad 不能在自身之外执行操作,因此IO不能执行Handler操作,但由于Handler包含IO在其内部,因此IO可以将操作“提升”一个级别。monad 转换器的真正有用之处在于它们可以无限分层,它本质上可以让您将不同 monad 的行为组合在一起。例如,想象一下如果你有一个Maybe动作,但你还想要State功能、Writer功能和IO功能。使用 monad 转换器,这成为可能,如果有点复杂的话。但是,这些组合的顺序通常很重要,因为可以提升操作,但不能降低操作。

于 2013-09-29T15:21:16.397 回答