2013-10-17 18 views
3

我想將Haskell添加到我的工具箱中,所以我正在通過Real World Haskell進行工作。hGetContents如何實現記憶效率?

在輸入和輸出的一章,在the section on hGetContents,我碰到這個例子就是:

import System.IO 
import Data.Char(toUpper) 

main :: IO() 
main = do 
    inh <- openFile "input.txt" ReadMode 
    outh <- openFile "output.txt" WriteMode 
    inpStr <- hGetContents inh 
    let result = processData inpStr 
    hPutStr outh result 
    hClose inh 
    hClose outh 

processData :: String -> String 
processData = map toUpper 

在此之後的代碼示例,作者繼續說:

注意hGetContents處理我們所有的閱讀。另外,請看processData。這是一個純函數,因爲它沒有副作用,每次調用時都會返回相同的結果。它不需要知道,也沒有辦法告訴 - 在這種情況下,它的輸入是從文件中懶懶地讀取的。 它可以在磁盤上使用20個字符或500GB數據轉儲的情況下很好地工作。(NB重點是我的)

我的問題是:如何做hGetContents或其結果值達到這個記憶效率,而不 - 在這個例子 - processData「能說」,仍保留所有好處累積到純代碼(即processData),特別是memoization?

<- hGetContents inh返回字符串所以inpStr勢必String類型的值,這正是processData接受的類型。但是,如果我正確理解Real World Haskell的作者,那麼這個字符串與其他字符串並不完全相同,因爲它沒有完全加載到內存中(或者完全評估,如果沒有完全評估的字符串存在..等等。 )在致電processData之前。

因此,另一種方式來問我的問題是:如果inpStr尚未完全在調用processData的時間進行評估或加載到內存中,然後又怎能用來查找,如果到processData一個memoized呼叫存在,不首先充分評估inpStr

是否存在類型String的實例,每個實例的行爲都有所不同,但在這個抽象層次上不能區分開來?

回答

4

hGetContents返回的String與任何其他Haskell字符串沒有區別。一般來說,除非程序員已採取額外步驟以確保Haskell數據(例如seq,deepseq,bang模式),否則Haskell數據不會被完全評估。

字符串被定義爲(基本上)

data List a = Nil | Cons a (List a) -- Nil === [], Cons === : 
type String = List Char 

這意味着,一個字符串是空,或單個字符(頭)和另一個字符串(尾部)。由於laziness,尾巴可能不存在於內存中,甚至可能是無限的。在處理String時,Haskell程序通常會檢查它是否爲NilCons,然後根據需要進行分支和處理。如果函數不需要評估尾部,它不會。此功能,例如,只需要檢查初始構造:

safeHead :: String -> Maybe Char 
safeHead [] = Nothing 
safeHead (x:_) = Just x 

這是一個完全合法的字符串

allA's = repeat 'a' :: String 

是無限的。你可以合理地操作這個字符串,但是如果你試圖打印所有的字符串,或者計算長度,或者任何無限的遍歷,程序都不會終止。但是你可以使用像safeHead這樣的函數,不會有任何問題,甚至會消耗一些有限的初始子字符串。

但是,你的直覺表明發生了奇怪的事情是正確的。 hGetContents是使用特殊函數unsafeInterleaveIO實現的,它實質上是一個編譯器鉤入IO的行爲。對此的評價越少越好。

如果沒有充分評估參數,就很難檢查是否存在對函數的memoized調用。但是,大多數編譯器不執行此優化。問題在於編譯器很難確定何時值得記憶呼叫,並且通過這樣做非常容易消耗所有的內存。幸運的是,您可以使用several memoizing libraries在適當時添加記憶。

+0

非常感謝您的回答。 爲了滿足我的好奇心,你能告訴我GHC是否執行任何記憶,或者是否因爲剛纔提到的原因而沒有進行任何優化? 我猜想用原子參數記憶函數(即沒有數據結構,如列表)會更容易實現。 – Marcel

+2

GHC根本不執行任何自動記憶功能。但它確實確保變量的值只計算一次。 – kqr

+0

@MarcelOomens:通常很難回答,但是簡單枚舉上的分支(例如'data Foo = Foo | Bar | Baz')通常會變成memoized調用。這似乎是通常的GHC優化轉換的結果,我不認爲它有任何特別的支持。 –