Показать прогресс программы Haskell - программирование
Подтвердить что ты не робот

Показать прогресс программы Haskell

У меня есть список в Haskell с некоторыми объектами. И мне нужно выяснить, удовлетворил ли кто-то из этих объектов определенное состояние. Итак, я написал следующее:

any (\x -> check x) xs

Но проблема в том, что операция проверки очень дорога, и список довольно большой. Я хочу увидеть текущий прогресс во время выполнения, например 50% (1000/2000 checked).
Как я могу это сделать?

4b9b3361

Ответ 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", когда вы нашли элемент, который проходит проверку.