如何监视Haskell中的计算过程

时间:2013-09-06 19:51:17

标签: haskell

我的主要栏目中有一个功能

map anyHeavyFunction [list]

我想在计算过程中显示进度条或添加其他操作(暂停,停止进程等),但由于map是纯函数,我不能直接执行。我猜我必须使用monad,但是monad是合适的吗? IOState

3 个答案:

答案 0 :(得分:7)

我知道至少有一个关于hackage的库有一些预先制作的monad变换器用于此任务,但我通常会在需要时转向管道包以自行滚动。我使用的是管道 - 4.0.0本周末将会出现在hackage上,但你可以在此之前从github repo获取它。

我还使用了terminal-progress-bar package so that it makes a nice terminal animation as well.

{-# 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 n l
        !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

动画:

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

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

每次更新都会删除最后一个栏,因此它只占用终端上的一行。然后就像这样结束:

> 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]

答案 1 :(得分:2)

这是一种我不满意的(简单的)答案。它基于这样一个事实:@shellenberg希望在一个(据称长)列表的每个元素上应用重函数。如果足以为列表的每个元素移动“进度条”一次,则可以将以下内容转换为通用解决方案。

首先,你需要选择你将要工作的monad。这取决于你的“进度条”到底是什么。对于此讨论,假设IO monad已经足够,我们希望交替显示字符-/|\。你也(很可能)需要某种状态S(这里只是到目前为止处理的元素数量,因此SInt),所以使用了真正的monad将是StateT S IO

假设您的原始程序是:

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"

(请注意,非常方便的是,重函数对列表的每个元素都需要相同的时间。)

这是你可以将它转换为显示原始“进度条”的方法:

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"

一个有趣的观点:seq中的tick强制评估列表中每个元素的结果。如果结果具有基本类型(此处Bool),这就足够了。否则,你不清楚你想做什么 - 记得Haskell很懒!

如果一个人想要一个更好的进度条,或者如果一个人不满意假设一个“tick”将被计入列表的每个元素,那么我认为有必要在重函数的逻辑中加入滴答声。这让它变得丑陋......我想看看能给出什么样的通用解决方案。我全都参与了Haskell,但我认为这对进度棒这样的东西很糟糕......没有免费的午餐;你不能变得纯洁和懒惰,让你的进度棒变得容易!


编辑:使用@Davorak建议的ProgressBar模块的版本。它肯定比我的旋转棒好看。

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"

这个想法是一样的,也有缺点。

答案 2 :(得分:0)

您可以使用parMap并行应用昂贵的函数(如果依赖项允许)和对应于每个列表(或块)元素的TVars列表并将它们设置为一次相应的功能应用程序已完成。一个单独的线程可以检查值并更新显示(显然这里会发生一些IO动作。)