在Yesod中,我们可以调用一个方法,该方法从只返回IO()的方法返回Handler()

时间:2013-09-29 15:08:10

标签: haskell yesod

请原谅我的无知但是,是否有办法从仅返回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内构建FoundationApplication.hs)对象并将其传递给我的代码时,抓住它。
  • 然后为消息回调创建curried function
  • 在curried函数中,我坚持使用Yesod构建的配置对象,同时构建基础
  • 我想出来后很轻松,使用配置对象我可以从settings.yml读取我的所有设置,甚至保留一个并行连接池。
  • 此连接池将用于从邮件回调中触发我的所有查询。
  • 不仅如此,而且因为我使用了这种方法,我免费登录,我可以在控制台上查看所有查询而无需编写任何代码。

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

2 个答案:

答案 0 :(得分:1)

首先,你的缩进看了。 Haskell中的白色空间非常重要。但更重要的是,单一行动中的每一个陈述都必须在同一个单子中:

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动作“提升”到Handler monad中,但这只是一种方式。要将Handler操作转换为IO操作,库通常会提供类似runHandler :: Handler [-> other args] -> IO ()的功能。我对Yesod并不是特别熟悉,但是在许多库中,模式类似。此函数用于将整个Handler操作转换为IO操作,通常保留用于运行服务器本身。

复杂的答案是Handler类型是所谓的 monad转换器。起初他们学习起来相当棘手,但你可以把它想象成一个包含另一个monad的monad。 Monads无法在自己之外执行操作,因此IO无法执行Handler操作,但由于Handler内包含IO,因此可以执行IO操作“提升”了一个级别。 monad变换器真正有用的是它们可以无限分层,它本质上可以让你将不同monad的行为组合在一起。例如,假设您有Maybe操作,但您还需要State功能,Writer功能和IO功能。使用monad变换器,如果有点复杂,这就成为可能。但是,这些组合的顺序通常很重要,因为操作可以解除,但不能降低。

答案 1 :(得分:0)

在最近的版本中,您可以使用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 ...