显示Haskell程序的进度

时间:2013-10-13 09:29:49

标签: haskell functional-programming

我在Haskell中列出了一些对象。我需要找出这些物体中的某个人是否满足某些条件。所以,我写了以下内容:

any (\x -> check x) xs

但问题是检查操作非常昂贵,而且列表非常大。我想在运行时看到当前的进展,例如50% (1000/2000 checked).
我怎么能这样做?

5 个答案:

答案 0 :(得分:17)

由于您希望查看函数的进度(这是函数的副作用),因此最明显的解决方案是使用monad。所以要做的第一件事是制作any函数的monadic版本:

anyM :: (Monad m) => (a -> m Bool) -> [a] -> m Bool
anyM _ []        = return False
anyM pred (x:xs) = reduce (pred x) xs
    where reduce acc []     = acc
          reduce acc (x:xs) = do
              condition <- acc
              if condition
                  then return condition
                  else reduce (pred x) xs

上述函数anyMany函数的monadic版本。除了检查给定列表中的任何项是否满足给定谓词外,它还允许我们产生副作用。

除了执行anyM函数之外,我们还可以使用any函数创建另一个显示进度条作为副作用的函数,如下所示:

anyVar :: (a -> Bool) -> [a] -> IO Bool
anyVar pred xs = anyM check $ zip [1..] xs
    where check (n,x) = do
            putStrLn $ show n ++ " checked. "
            return $ pred x

请注意,由于我们事先不知道列表的长度,因此我们只显示选中列表中的项目数。如果我们事先知道列表中的项目数,那么我们可以显示更具信息性的进度条:

anyFix :: (a -> Bool) -> Int -> [a] -> IO Bool
anyFix pred length xs = anyM check $ zip [1..] xs
    where check (n,x) = do
            putStrLn $ show (100 * n `div` length) ++ "% (" ++
                show n ++ "/" ++ show length ++ " checked). "
            return $ pred x

anyVar函数用于无限列表以及事先不知道其长度的列表。将anyFix函数用于事先知道其长度的有限列表。

如果列表很大并且您事先不知道列表的长度,那么length函数将需要遍历整个列表以确定其长度。因此,最好使用anyVar代替。

最后要将它包装起来,这就是你如何使用上述功能:

main = anyFix (==2000) 2000 [1..2000]

在您的情况下,您可以执行以下操作:

main = anyVar check xs

希望这个答案对你有所帮助。

答案 1 :(得分:12)

另一种方法是使用conduitpipes等流媒体库。下面是一些使用pipes的示例代码,每次列表中的元素到达时都会打印一个点:

import Pipes
import qualified Pipes.Prelude as P 

bigList :: [Int]
bigList = [1,2,3,4]

check :: Int -> Bool
check = (>3)

main :: IO ()
main = do
    result <- P.any check $ each bigList >-> P.chain (\_ -> putStrLn ".")
    putStrLn . show $ result

each是Pipes模块中的一个函数。)

现在,如果你想显示百分比,那么管道的P.chain (\_ -> putStrLn ".")部分必须更加智能。它必须将当前百分比作为状态,并知道列表的长度。 (如果你的列表很大并且懒得生成,计算它的长度会强制进行评估并可能导致问题。如果你已经在内存中使用它,那就不会有什么问题。)

编辑:这是以前代码的可能扩展,实际上显示百分比:

{-# LANGUAGE FlexibleContexts #-}

import Pipes
import qualified Pipes.Prelude as P
import Data.Function
import Control.Monad.RWS

bigList :: [Int]
bigList = [1,2,3,4]

check :: Int -> Bool
check = (>3)

-- List length is the environment, number of received tasks is the state. 
tracker :: (MonadReader Int m, MonadState Int m, MonadIO m) => Pipe a a m r
tracker = P.chain $ \_ -> do
    progress <- on (/) fromIntegral `liftM` (modify succ >> get) `ap` ask
    liftIO . putStrLn . show $ progress

main :: IO ()
main = do
    (result,()) <- evalRWST (P.any check $ each bigList >-> tracker)
                            (length bigList) -- list length as unchanging environment
                            0 -- initial number of received tasks (the mutable state)
    putStrLn . show $ result

可以进一步细化以显示仅显着的百分比增长。

答案 2 :(得分:7)

最天真直接的方法是实施自己的

anyM :: (a -> Bool) -> [a] -> IO Bool

打印进度条(例如,使用terminal-progress-bar)。

但请注意,为了计算百分比,您必须评估完整列表。这会破坏懒惰,并且可能对程序的空间行为产生不良和不良影响。

还有一些使用unsafePerformIOunsafeInterleaveIO的方法可以监控纯计算(例如any),有关示例,请参阅bytestring-progress。但这是可疑的设计,只有当你知道自己了解后果时才应该使用。

答案 3 :(得分:3)

我只会使用Debug.Trace.trace并跟踪当前位置,如下所示:

any (\(i,x) -> trace (showProgress i (length xs)) $ check x) $ zip [1..] xs

答案 4 :(得分:0)

当您找到通过检查的元素时,您可以使用库来显式异常,例如explicit-exception:Control.Monad.Execption.Synchronoustransformers:Control.Monad.Trans.Maybe和“抛出异常”。