我在 Haskell 中有一些对象的列表。我需要找出这些对象中的某个人是否满足某些条件。所以,我写了以下内容:
any (\x -> check x) xs
但问题是检查操作非常昂贵,而且列表很大。我想查看运行时的当前进度,例如50% (1000/2000 checked).
我该怎么做?
我在 Haskell 中有一些对象的列表。我需要找出这些对象中的某个人是否满足某些条件。所以,我写了以下内容:
any (\x -> check x) xs
但问题是检查操作非常昂贵,而且列表很大。我想查看运行时的当前进度,例如50% (1000/2000 checked).
我该怎么做?
由于您想查看函数的进度(这是函数的副作用),最明显的解决方案是使用 monads。所以首先要做的是制作any
函数的一元版本:
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
上述函数anyM
是该函数的一元版本any
。除了检查给定列表中的任何项目是否满足给定谓词之外,它还允许我们产生副作用。
除了执行如下函数之外,我们还可以使用该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
希望这个答案对你有所帮助。
另一种方法是使用流库,如conduit
or 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
(每个都是 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
它可以进一步细化以仅显示显着的百分比增长。
最天真最直接的方式就是自己实现
anyM :: (a -> Bool) -> [a] -> IO Bool
打印进度条(例如使用terminal-progress-bar)。
但请注意,为了计算百分比,您必须评估完整列表。这打破了懒惰,并且可能对程序的空间行为产生不良和不良影响。
还有一些方法使用unsafePerformIO
andunsafeInterleaveIO
可以让您监控纯计算(例如any
),请参阅bytestring-progress示例。但这是一个可疑的设计,只有在您知道自己了解后果时才应该使用它。
我会Debug.Trace.trace
像这样使用并跟踪当前位置:
any (\(i,x) -> trace (showProgress i (length xs)) $ check x) $ zip [1..] xs
当您找到通过检查的元素时,您可以将库用于显式异常,例如explicit-exception:Control.Monad.Execption.Synchronous
ortransformers:Control.Monad.Trans.Maybe
和“抛出异常”。