【问题标题】:Collecting IO outputs into list将 IO 输出收集到列表中
【发布时间】:2011-02-20 02:40:14
【问题描述】:

如何向SDL.pollEvent :: IO Event 发出多次调用,直到输出为SDL.NoEvent 并将所有结果收集到一个列表中?

用命令式的说法是这样的:

events = []
event = SDL.pollEvent
while ( event != SDL.NoEvent ) {
        events.add( event )
        event = SDL.pollEvent
}

【问题讨论】:

    标签: haskell io monads


    【解决方案1】:

    James Cook 很乐意用这个功能扩展 monad-loops:

    unfoldWhileM  :: Monad  m => (a -> Bool) -> m a -> m [a]
    

    与 SDL 一起使用:

    events <- unfoldWhileM (/= SDL.NoEvent) SDL.pollEvent
    

    【讨论】:

      【解决方案2】:

      将这些存根用于EventpollEvent

      data Event = NoEvent | SomeEvent
        deriving (Show,Eq)
      
      instance Random Event where
        randomIO = randomRIO (0,1) >>= return . ([NoEvent,SomeEvent] !!)
      
      pollEvent :: IO Event
      pollEvent = randomIO
      

      和一个从an earlier answer借用和改编的组合器,它在谓词第一次失败时停止评估

      spanM :: (Monad m) => (a -> Bool) -> m a -> m [a]
      spanM p a = do
        x <- a
        if p x then do xs <- spanM p a
                       return (x:xs)
               else return [x]
      

      允许这个 ghci 会话,例如:

      *Main> spanM (/= NoEvent) pollEvent
      [SomeEvent,SomeEvent,NoEvent]

      【讨论】:

        【解决方案3】:

        我最终在一个来自 hackage 的实际 SDL 游戏中偶然发现了这段代码 sn-p

        getEvents :: IO Event -> [Event] -> IO [Event]
        getEvents pEvent es = do
          e <- pEvent
          let hasEvent = e /= NoEvent
          if hasEvent
           then getEvents pEvent (e:es)
           else return (reverse es)
        

        顺便说一句,谢谢你的回答!

        【讨论】:

        • 如果这是一种非常流行的方法,可以一次性取出队列中等待的所有事件而不对其进行处理,那么为什么 SDL API 不直接提供它呢?这可能有助于避免一些同步。线程安全队列的开销。
        【解决方案4】:

        你可以使用类似的东西:

        takeWhileM :: (a -> Bool) -> IO a -> IO [a] takeWhileM p act = do x

        代替:

        做
          xs 
        
        

        你也可以使用:

        liftM (x:) (takeWhileM p act) 屈服:

        takeWhileM :: (a -> Bool) -> IO a -> IO [a] takeWhileM p act = do x

        那么你可以使用:takeWhileM (/=SDL.NoEvent) SDL.pollEvent

        【讨论】:

        • 我建议takeUntilM :: Monad m =&gt; (a -&gt; Bool) -&gt; m a -&gt; m [a](当p xfalse 时使用适当的return [x])以避免信息丢失(尤其是来自IO monads)。当它只是 SDL.NoEvent 时可能看起来很正常,但对于 Left "system crash" :: Either String a 可能是错误的。
        • @peaker:恕我直言,使用单子列表更加模块化
        • @ony:为什么懒惰?这是 SDL 应用程序主循环中的轮询事件。从逻辑上讲,最好考虑 SDL 将事件推送到程序,而不是程序按需拉事件。事实上,如果你没有足够快地清除事件队列,我认为 SDL 会失败。有时懒惰是没有意义的。
        • @ony:lazy-IO 容易出错。但是,如果有人仍然坚持这样做,我不认为创建 takeWhileM 的懒惰 IO 版本是一种很好的模块化方法。转换为惰性列表应作为takeWhile 的单独步骤完成。这可以使用一元列表来实现。 (下面我自己的答案无耻插入)
        【解决方案5】:

        您可以使用一元列表:

        import Control.Monad.ListT (ListT)
        import Control.Monad.Trans.Class (lift) -- transformers, not mtl
        import Data.List.Class (takeWhile, repeat, toList)
        import Prelude hiding (takeWhile, repeat)
        
        getEvents :: IO [Event]
        getEvents = 
            toList . takeWhile (/= NoEvent) $ do
                repeat ()
                lift pollEvent :: ListT IO Event
        

        ListT 来自关于 hackage 的“列表”包。

        【讨论】:

        • @peaker: repeat () :: ListT IO () 是一个无限的 IO-monadic 列表,其中包含无关紧要的值 (())。然后我们用lift pollEvent(&gt;&gt;) 它,这样对于无限列表的每个元素我们pollEventtakeWhile 使它成为一个有限单子列表,然后 toList 使它成为 :: IO [Event]
        • 这似乎有点奇怪.. 使用“repeatM (lift pollEvent)”之类的东西可能更有意义?
        • @peaker:是的,repeatM pollEvent。我在 github 树中添加了repeatM,它将在下一个 hackage 版本中出现
        猜你喜欢
        • 2016-01-04
        • 2021-02-25
        • 1970-01-01
        • 1970-01-01
        • 2014-12-23
        • 2013-10-21
        • 1970-01-01
        • 1970-01-01
        • 2013-10-07
        相关资源
        最近更新 更多