再次打破 Haskell 中的循环

Again on breaking loops in Haskell

我有一小段代码在 zeromq Pull 套接字上接收帧并在 opencv 中显示它 window:

module Main where

import           Control.Monad
import qualified OpenCV as CV
import           System.ZMQ4.Monadic
import           System.Exit

main :: IO()
main = runZMQ $ do
  receiver <- socket Pull
  bind receiver "tcp://*:5554"

  -- do some stuff not relevant

  forever $ do
    buffer <- receive receiver
    let img = CV.imdecode CV.ImreadUnchanged buffer -- simple decoder
    liftIO $ CV.withWindow "Video" $ \window -> do
        CV.imshow window img
        key <- CV.waitKey 10
        when (key == 27) exitSuccess -- <- UGLY!

我想找到一种打破循环的方法,让我有更多的控制权。我知道 Gabriel Gonzalez here(我非常喜欢)提出的 EitherT 解决方案,但我无法在 CV.withWindow 上下文中实现它,例如:

quit :: (Monad m) => e -> EitherT e m r
quit = left

loop :: (Monad m) => EitherT e m a -> m e
loop = fmap (either id id) . runEitherT . forever

main :: IO()
main = runZMQ $ do
  receiver <- socket Pull
  bind receiver "tcp://*:5554"

  loop $ do
    buffer <- receive receiver
    let img = CV.imdecode CV.ImreadUnchanged buffer -- simple decoder
    liftIO $ CV.withWindow "Video" $ \window -> do
        CV.imshow window img
        key <- CV.waitKey 10
        when (key == 27) $ quit ()

但当然 quit 将参数包装在 Left 中,此解决方案无法编译。

读写IORef,并使用whileM_

main = runZMQ $ do
    receiver <- socket Pull
    bind receiver "tcp://*:5554"
    continue <- liftIO $ newIORef True

    whileM_ (liftIO $ readIORef continue) $ do
        buffer <- receive receiver
        let img = CV.imdecode CV.ImreadUnchanged buffer -- simple decoder
        liftIO . CV.withWindow "Video" $ \window -> do
            CV.imshow window img
            key <- CV.waitKey 10
            when (key == 27) $ writeIORef continue False

或者让你的循环适当地显式调用自身:

main = runZMQ $ do
    receiver <- socket Pull
    bind receiver "tcp://*:5554"

    let loop = do
            buffer <- receive receiver
            let img = CV.imdecode CV.ImreadUnchanged buffer -- simple decoder
            key <- liftIO . CV.withWindow "Video" $ \window -> do
                CV.imshow window img
                CV.waitKey 10
            when (key /= 27) loop

    loop

如何将回调 return 设为 Either () (),然后将其包装在 ExceptT 中,然后再传递给永远?像

runExceptT . forever . ExceptT $ do
  buffer <- receive receiver
  let img = CV.imdecode CV.ImreadUnchanged buffer -- simple decoder
  liftIO $ CV.withWindow "Video" $ \window -> do
      CV.imshow window img
      key <- CV.waitKey 10
      if (key == 27) 
          then (return (Left ()))
          else (return (Right ()))