2013-09-06 4 views
7

में गणना प्रक्रिया की निगरानी करने के लिए कैसे मैंहास्केल

map anyHeavyFunction [list] 

मैं गणना की प्रक्रिया के दौरान एक प्रगति बार दिखा सकते हैं या अतिरिक्त क्रियाओं को जोड़ने के लिए (रोकें, प्रक्रिया को रोकने आदि) करना चाहते हैं मेरी मुख्य ब्लॉक में एक समारोह है , लेकिन क्योंकि map एक शुद्ध कार्य है जिसे मैं सीधे नहीं कर सकता। मुझे लगता है कि मुझे monads का उपयोग करना है, लेकिन क्या monad उपयुक्त है? IO, State?

उत्तर

7

मुझे पता है कि हैकेज पर कम से कम एक लाइब्रेरी है जिसमें इस कार्य के लिए कुछ पूर्व निर्मित मोनैड ट्रांसफार्मर हैं, लेकिन जब मैं एक की आवश्यकता होती हूं तो मैं आमतौर पर पाइप पैकेज में बदल जाता हूं। मैं पाइप-4.0.0 का उपयोग कर रहा हूं, यह इस सप्ताह के अंत में हैकेज पर होगा, लेकिन इससे पहले कि आप इसे 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] 
+0

ध्यान दें कि यह 'toListM' के उपयोग के कारण बड़ी सूचियों पर बह जाएगा। निरंतर मेमोरी में परिणामों को स्ट्रीम करने के लिए आप 'for' लूप का उपयोग कर सकते हैं जैसे 'runEffect $ (प्रत्येक [1..100]> -> पी। मैप भारी> -> प्रगति 100) (लिफ्ट प्रिंट)' या आप 'पी.प्रिंट' सुविधा 'उपभोक्ता' का उपयोग कर सकते हैं। –

+0

@ गैब्रियल गोंज़ालेज़ - मुझे क्या याद है कि एक आलसी सूची में पाइप को स्ट्रीम करने का सबसे अच्छा तरीका एक आलसी लेखक मोनैड के साथ था, आलसी लेखक मोनद में निर्मित आलसी पर्याप्त नहीं था। आलसी सूची में स्ट्रीमिंग के लिए कोई बेहतर तरीका है? – Davorak

+0

'toList' पुराने आलसी लेखक मोनैड चाल को प्रतिस्थापित करता है, हालांकि उन दो समाधानों में से कोई भी शुद्ध और आलसी सूची में' निर्माता 'को अशुद्ध नहीं कर सकता है। हालांकि, इसकी कोई ज़रूरत नहीं है, क्योंकि 'निर्माता' ** ** आभासी आलसी सूची है जो आप चाहते हैं और बेवकूफ चीज सिर्फ इसे 'निर्माता' के रूप में छोड़ना है। –

0

आप parMap का उपयोग समानांतर में महंगा समारोह (यदि निर्भरता अनुमति देते हैं) और एक प्रत्येक सूची के लिए इसी (या का हिस्सा) TVars की सूची तत्व (रों) लागू करते हैं और उन्हें सेट संबंधित समारोह आवेदन पूरा कर लिया है एक बार करने के लिए कर सकता है। एक अलग धागा मूल्यों की जांच कर सकता है और प्रदर्शन को अपडेट कर सकता है (स्पष्ट रूप से कुछ IO कार्रवाई यहां होगी)।

2

यहां एक (प्रकार का) सरल उत्तर है जिसे मैं संतुष्ट नहीं हूं। यह इस तथ्य पर आधारित है कि @shellenberg एक (माना जाता है) सूची के प्रत्येक तत्व पर एक भारी कार्य लागू करना चाहता था। यदि सूची के प्रत्येक तत्व के लिए एक बार "प्रगति पट्टी" को स्थानांतरित करने के लिए पर्याप्त है, तो निम्नलिखित को सामान्य समाधान में बदल दिया जा सकता है।

सबसे पहले, आपको उस मोनैड को चुनने की आवश्यकता है जिसमें आप काम करेंगे। यह इस बात पर निर्भर करता है कि आपकी "प्रगति पट्टी" वास्तव में क्या है। इस चर्चा के लिए, मान लीजिए कि IO मोनैड पर्याप्त है और हम वैकल्पिक रूप से वर्ण -, /, | और \ प्रदर्शित करना चाहते हैं। आपको भी (शायद सबसे अधिक) किसी प्रकार की स्थिति की आवश्यकता होगी S (यहां यह केवल अब तक संसाधित तत्वों की संख्या है, इसलिए SInt है), इसलिए वास्तविक मोनड 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" 

एक दिलचस्प बिंदु:: के लिए परिणाम की seqtick में सेना मूल्यांकन कच्चे "प्रगति बार" प्रदर्शित करने के लिए सूची के प्रत्येक तत्व। यह पर्याप्त है, यदि परिणाम का मूल प्रकार है (Bool यहां)। अन्यथा, यह स्पष्ट नहीं है कि आप क्या करना चाहते हैं - याद रखें हास्केल आलसी है!

यदि कोई बेहतर प्रगति पट्टी चाहता है या यदि कोई इस धारणा से संतुष्ट नहीं है कि सूची के प्रत्येक तत्व के लिए एक "टिक" गिना जाएगा, तो मुझे विश्वास है कि भारी के तर्क में टिकिंग को शामिल करना आवश्यक है समारोह। यह बदसूरत बनाता है ... मैं देखना चाहता हूं कि किस प्रकार के सामान्य समाधान सुझाए जा सकते हैं। मैं सभी हास्केल के लिए हूं, लेकिन मुझे लगता है कि यह प्रगति सलाखों जैसी चीजों के लिए बेकार है ... कोई मुफ्त भोजन नहीं है; आप शुद्ध और आलसी नहीं हो सकते हैं और आपकी प्रगति सलाखों को आसान बना दिया है!


संपादित करें: जो 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" 

विचार समान है, दोष भी है।