How to control the calculation process in Haskell

I have a function in the main unit

map anyHeavyFunction [list] 

I want to show a progress bar during the calculation process or add additional actions (pause, stop, etc.), but since map is a pure function, I cannot do it directly. I can assume that I should use monads, but which monad is appropriate? IO , State ?

+7
haskell
source share
3 answers

I know that for this task there is at least one library in which there are some ready-made monad transformers, but I usually turn to a packet of pipes to roll on my own when I need it. I am using pipe-4.0.0, it will be in the hack this weekend, but you can grab it from github repo before that.

I also used the terminal-progress-bar package to make nice animation of the terminal.

 {-# language BangPatterns #-} import Pipes import qualified Pipes.Prelude as P import Control.Monad.IO.Class import System.ProgressBar import System.IO ( hSetBuffering, BufferMode(NoBuffering), stdout ) -- | Takes the total size of the stream to be processed as l and the function -- to map as fn progress l = loop 0 where loop n = do liftIO $ progressBar (msg "Working") percentage 40 nl !x <- await -- bang pattern to make strict yield x loop (n+1) main = do -- Force progress bar to print immediately hSetBuffering stdout NoBuffering let n = 10^6 let heavy x = last . replicate n $ x -- time wasting function r <- P.toListM $ each [1..100] >-> P.map heavy >-> progress 100 putStrLn "" return r 

This animates:

 > Working [=>.......................] 7% > Working [=====>...................] 20% 

Each update erases the last bar, so it only takes up one line on the terminal. Then it ends like this:

 > main Working [=========================] 100% [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100] 
+7
source share

Here's (sort of) a simple answer that doesn't suit me. It is based on the fact that @shellenberg wanted to apply a heavy function to each element of a (supposedly long) list. If it is enough to move the “progress bar” once for each element of the list, then the following can be turned into a general solution.

First of all, you need to choose the monad in which you will work. It depends on what your “progress indicator” is. For this discussion, suppose that the IO monad is enough and we want to display the characters - , / , | and \ . You also (most likely) will need some state S (here only the number of processed elements, therefore S is Int ), so the real monad used will be StateT S IO .

Suppose your original program:

 m = 100000 -- how many elements the list has -- Your (pure) function anyHeavyFunction :: Int -> Bool anyHeavyFunction n = length [1..n] + length [n+1..4217] == 4217 -- Your list list :: [Int] list = take m $ repeat 4217 -- The main program main :: IO () main = do let l = map anyHeavyFunction list if and l then putStrLn "OK" else putStrLn "WRONG" 

(Note that a very conveniently heavy function takes the same time for each item in the list.)

Here's how you could convert it to display a crude "progress bar":

 import Control.Monad.State import System.IO (hFlush, stdout) m = 100000 -- how many elements the list has k = 5000 -- how often you want to "tick" tick :: a -> StateT Int IO a tick x = do s <- get put $ s+1 when (s `mod` k == 0) $ liftIO $ do let r = (s `div` k) `mod` 4 putChar $ "-/|\\" !! r putChar '\b' hFlush stdout x `seq` return x -- Your (pure) function anyHeavyFunction :: Int -> Bool anyHeavyFunction n = length [1..n] + length [n+1..4217] == 4217 -- Your list list :: [Int] list = take m $ repeat 4217 -- The main program main :: IO () main = do l <- flip evalStateT 0 $ mapM (tick . anyHeavyFunction) list if and l then putStrLn "OK" else putStrLn "WRONG" 

An interesting point: seq in tick forcibly evaluates the result for each element of the list. This is sufficient if the result is of the base type ( Bool here). Otherwise, it is not clear what you want to do - remember that Haskell is lazy!

If a thinner progress bar is required or if you are not satisfied with the assumption that one “tick” will be counted for each element of the list, then I consider it necessary to include a tick in the logic of a heavy function. This makes it ugly ... I would like to see what common solutions may be offered for this. I'm all at Haskell, but I think it sucks for things like progress bars ... There is no free lunch; you cannot be clean and lazy, and your progress bars have become easier!


EDIT: A version that uses the ProgressBar module proposed by @Davorak. It certainly looks better than my spinning bar.

 import Control.Monad.State import System.ProgressBar import System.IO (hSetBuffering, BufferMode(NoBuffering), stdout) m = 100000 -- how many elements the list has k = 5000 -- how often you want to "tick" tick :: a -> StateT Int IO a tick x = do s <- get put $ s+1 when (s `mod` k == 0) $ liftIO $ do progressBar (msg "Working") percentage 40 (toInteger s) (toInteger m) x `seq` return x -- Your (pure) function anyHeavyFunction :: Int -> Bool anyHeavyFunction n = length [1..n] + length [n+1..4217] == 4217 -- Your list list :: [Int] list = take m $ repeat 4217 -- The main program main :: IO () main = do hSetBuffering stdout NoBuffering l <- flip evalStateT 0 $ mapM (tick . anyHeavyFunction) list if and l then putStrLn "OK" else putStrLn "WRONG" 

The idea is the same and the flaws.

+2
source share

You can use parMap for parallel operation of an expensive function (if dependencies are allowed) and a TVars list corresponding to each element (or blocks) of the list, and install them after the corresponding function application is completed. A separate thread can check the values ​​and update the display (obviously, several IO actions will happen here).

0
source share

All Articles