Показать прогресс программы Haskell
У меня есть список в Haskell с некоторыми объектами. И мне нужно выяснить, удовлетворил ли кто-то из этих объектов определенное состояние. Итак, я написал следующее:
any (\x -> check x) xs
Но проблема в том, что операция проверки очень дорога, и список довольно большой. Я хочу увидеть текущий прогресс во время выполнения, например 50% (1000/2000 checked).
Как я могу это сделать?
Ответы
Ответ 1
Поскольку вы хотите видеть прогресс вашей функции (что является побочным эффектом функции), наиболее очевидным решением является использование монадов. Итак, первое, что нужно сделать, это сделать монадическую версию функции 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
Надеюсь, этот ответ помог вам.
Ответ 2
Другой способ сделать это - использовать поточную библиотеку типа conduit
или pipes
. Вот пример кода с помощью 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
Он может быть дополнительно уточнен, чтобы показать только значительное увеличение процента.
Ответ 3
Самый наивный и прямой способ - реализовать свой собственный
anyM :: (a -> Bool) -> [a] -> IO Bool
который печатает индикатор выполнения (например, используя terminal-progress-bar).
Но учтите, что для вычисления процента вам нужно будет оценить полный список. Это нарушает ленивость и может иметь плохие и нежелательные последствия для пространственного поведения программы.
Существуют также подходы, использующие unsafePerformIO
и unsafeInterleaveIO
, которые позволяют контролировать чистый расчет (например, any
), см. bytestring-progress для примера. Но это сомнительный дизайн, который вы должны использовать, только если знаете, что понимаете последствия.
Ответ 4
Я бы просто использовал Debug.Trace.trace
и отслеживал текущую позицию следующим образом:
any (\(i,x) -> trace (showProgress i (length xs)) $ check x) $ zip [1..] xs
Ответ 5
Вы можете использовать библиотеку для явных исключений, таких как explicit-exception:Control.Monad.Execption.Synchronous
или transformers:Control.Monad.Trans.Maybe
, и "throw exception", когда вы нашли элемент, который проходит проверку.