我的主块中有一个函数
map anyHeavyFunction [list]
我想在计算过程中显示进度条或添加其他操作(暂停、停止过程等),但由于map
是纯函数,我无法直接执行。我可以猜想我必须使用单子,但是什么单子是合适的?IO
, State
?
我的主块中有一个函数
map anyHeavyFunction [list]
我想在计算过程中显示进度条或添加其他操作(暂停、停止过程等),但由于map
是纯函数,我无法直接执行。我可以猜想我必须使用单子,但是什么单子是合适的?IO
, State
?
我知道至少有一个关于 hackage 的库有一些用于此任务的预制 monad 转换器,但是当我需要一个时,我通常会转向管道包来滚动我自己的。我正在使用管道 4.0.0,它将在本周末进行黑客攻击,但您可以在此之前从github存储库中获取它。
我还使用了 terminal-progress-bar 包,这样它也可以制作出漂亮的终端动画。
{-# 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]
这是我不满意的(某种)简单答案。这是基于@shellenberg 想要对(据说很长)列表的每个元素应用一个重函数的事实。如果为列表的每个元素移动一次“进度条”就足够了,那么以下可以变成一个通用的解决方案。
首先,您需要选择您将在其中工作的 monad。这取决于您的“进度条”到底是什么。对于本次讨论,假设monad 就足够了,并且IO
我们希望交替显示字符-
、/
和。您还(很可能)需要某种状态(这里只是到目前为止处理的元素数量,因此是),所以真正使用的 monad 将是.|
\
S
S
Int
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 是懒惰的!
如果您想要一个更精细的进度条,或者如果您对列表中的每个元素都会计算一个“滴答”的假设不满意,那么我认为有必要将滴答纳入重功能的逻辑中。这让它变得丑陋......我想看看可以建议什么样的通用解决方案。我完全支持 Haskell,但我认为进度条之类的东西很糟糕......没有免费的午餐;你不能单纯又懒惰,让你的进度条变得容易!
编辑:使用ProgressBar
@Davorak 建议的模块的版本。它肯定比我的旋转条看起来更好。
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"
思路是一样的,缺点也是一样。
您可以使用parMap
并行应用昂贵的函数(如果依赖项允许)和TVars
对应于每个列表(或块)元素的列表,并在相应的函数应用程序完成后设置它们。一个单独的线程可以检查这些值并更新显示(显然IO
这里会发生一些操作)。