2013-06-25 56 views
15

我正在選擇一個具體的任務來說明我在說什麼在haskell計算中「插入」進度記錄/跟蹤?

假設我想找到一個大數字的所有因素的總和,天真地 - 通過檢查下面的每個數字,如果它是一個因素,然後將它們加在一起。

與IO和純計算之間沒有分離的命令式編程語言,你可以做這樣的

def sum_of_factors(n): 
    sum = 0 
    for i between 1 and n: 
    if (n % i == 0): 
     sum += i 
    return sum 

不過的東西,如果我n大,我最終會在一個空的盯着屏幕計算完成之前很長時間。所以我添加一些日誌記錄 -

def sum_of_factors(n): 
    sum = 0 
    for i between 1 and n: 
    if (i % 1000 == 0): 
     print "checking $i..." 
    if (n % i == 0): 
     print "found factor $i" 
     sum += 1 
    return sum 

真的,這個增加是微不足道的。現在

,如果我是這樣做的教科書哈斯克爾我可以做

sum_of_factors :: Int -> Int 
sum_of_factors n = foldl' (+) 0 factors 
    where 
    factors = filter ((== 0) . (mod n)) [1..n] 

我遇到同樣的問題,因爲之前...爲大的數字,我只盯着一個空白屏幕一會兒。

但我無法弄清楚如何在Haskell代碼中注入相同類型的跟蹤/日誌記錄。我不確定,除了可能用顯式遞歸重新實現fold,以獲得與命令性不純代碼中相同的跟蹤模式/結果。

Haskell有一個教師可以做到這一點嗎?一個不需要重構所有東西?

謝謝

+3

對於調試,可以使用'Debug.Trace'中的'traceShow'(或相關函數)。但是我也會對非調試版本感興趣。 – Stefan

+1

注意:您的分部錯誤,'if(i%n == 0)'resp。 ''is_factor我=我'mod' n == 0''。另外,'(take n [1 ..])'應該是'[1 .. n]'。 –

+0

過濾((== 3)。(\'mod \'n))[1..n]'總是對'n> 3'產生'3'嗎?在'[1..n]'上映射'(\'mod \'n)'只會產生[1,2,... n-1,0] '是的,就是'3'。 –

回答

14

有一些可能的解決方案。

最簡單的就是改變你的函數返回事件流而不是最終結果。你sum_of_factors不適合我編譯,所以我將使用sum函數作爲例子。想法是發送Left message以顯示進度,並在完成後發送Right result。由於懶惰的評價,你會看到進步事件,而該功能的工作:

import Control.Monad 

sum' :: [Int] -> [Either String Int] 
sum' = go step 0 
    where 
    step = 10000 
    go _ res [] = [Right res] 
    go 0 res (x:xs) = Left ("progress: " ++ show x) : go step (res + x) xs 
    go c res (x:xs) = go (c - 1) (res + x) xs 

main :: IO() 
main = do 
    forM_ (sum' [1..1000000]) $ \event -> 
    case event of 
     Right res -> putStrLn $ "Result: " ++ show res 
     Left str -> putStrLn str 

其他(從我的角度來看更好)的解決方案是使功能單子:

class Monad m => LogM m where 
    logMe :: String -> m() 

instance LogM IO where 
    logMe = putStrLn 

sum' :: LogM m => [Int] -> m Int 
sum' = go step 0 
    where 
    step = 10000 
    go _ res [] = return res 
    go 0 res (x:xs) = logMe ("progress: " ++ show x) >> go step (res + x) xs 
    go c res (x:xs) = go (c - 1) (res + x) xs 

main :: IO() 
main = sum' [1..1000000] >>= print 

或使用foldM

import Control.Monad 

sum' :: LogM m => [Int] -> m Int 
sum' = liftM snd . foldM go (0, 0) 
    where 
    step = 10000 
    -- `!` forces evaluation and prevents build-up of thunks. 
    -- See the BangPatterns language extension. 
    go (!c, !res) x = do 
     when (c == 0) $ logMe ("progress: " ++ show x) 
     return $ ((c + 1) `mod` step, res + x) 
+0

我有點這樣說,但不是真的,但我正在尋找一種解決方案,如果可能,我不需要重新實現摺疊到顯式遞歸。原因是我希望保留摺疊的表現力,並且能夠快速「關閉」日誌記錄,而不用交換太多的功能。 –

+2

@JustinL。您可以使用[foldM](http://hackage.haskell.org/packages/archive/base/latest/doc/html/Control-Monad.html#v:foldM)來避免顯式遞歸。 –

+2

@JustinL。記錄是一個副作用,所以你必須通過流模擬或者通過monadic上下文明確定義。你可以用'enableLoggin','disableLoggin','WithoutLogging'等等方法擴展'LogM'來創建可配置的日誌框架。甚至可以在編譯時啓用/禁用日誌記錄,如@PetrPudlák建議的那樣。注意:「monadic」並不意味着「不純」。 – Yuras

10

如果你需要快速和骯髒的記錄,你可以使用Debug.Trace。它允許您快速將日誌記錄功能添加到純代碼中。 (當然,在這種情況下,它使用了不安全的東西。)準備好它的日誌輸出出現在你預期的不同時間(或者根本不會) - 這是將不純調試代碼添加到純粹計算中的結果,評估。

否則,您必須使用monadic代碼,才能正確排序日誌輸出。其中一個使用IO的開發庫是hslogger

如果你不想把你的代碼綁定到IO(這是非常明智的),Yuras的方法是要走的路。創建自己的monad類型類,描述你的日誌操作(可能有不同的級別等)。然後,有生產記錄輸出,就像在回答一個實例,一個實例不會做任何事情,就像

instance LogM Identity where 
    logMe _ = return() 

然後,只需通過切換,你正在使用的單子,你把洛/ off,並且編譯器優化了Identity monad。