[英]How does hGetContents achieve memory efficiency?
我想將Haskell添加到我的工具箱中,所以我正在通過Real World Haskell工作 。
在輸入和輸出的章節中,在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
接受的類型。 但是,如果我正確理解了真實世界Haskell的作者,那么這個字符串就不像其他字符串那樣,因為它沒有完全加載到內存中(或者如果存在未完全評估的字符串這樣的東西,則完全評估它。 。)在調用processData
。
因此,問我的問題的另一種方法是:如果在調用processData
時沒有對inpStr
進行全面評估或加載到內存中,那么如何在沒有首先完全評估inpStr
情況下查找是否存在對processData
的memoized調用inpStr
?
是否存在類型為String
實例,每個實例的行為都不同但在此抽象級別上無法分開?
hGetContents
返回的String
與任何其他Haskell字符串沒有區別。 通常,除非程序員采取額外步驟來確保它(例如seq
, deepseq
,bang模式),否則不會對Haskell數據進行全面評估。
字符串定義為(基本上)
data List a = Nil | Cons a (List a) -- Nil === [], Cons === :
type String = List Char
這意味着字符串是空的,或者是單個字符(頭部)和另一個字符串(尾部)。 由於懶惰 ,尾巴可能不存在於記憶中,甚至可能是無限的。 在處理String
,Haskell程序通常會檢查它是否為Nil
或Cons
,然后根據需要進行分支和繼續。 如果函數不需要評估尾部,則不會。 例如,此函數只需要檢查初始構造函數:
safeHead :: String -> Maybe Char
safeHead [] = Nothing
safeHead (x:_) = Just x
這是一個完全合法的字符串
allA's = repeat 'a' :: String
這是無限的。 您可以合理地操作此字符串,但是如果您嘗試打印所有字符串,或計算長度,或任何類型的無界遍歷,您的程序將不會終止。 但是你可以safeHead
使用safeHead
函數,甚至可以使用一些有限的初始子字符串。
然而,你發現某些奇怪事情的直覺是正確的。 hGetContents
是使用特殊函數unsafeInterleaveIO實現的 ,它本質上是IO
行為的編譯器鈎子。 對此越少說越好。
你是正確的,如果沒有完全評估參數,就很難檢查是否存在對函數的memoized調用。 但是,大多數編譯器不執行此優化。 問題是編譯器很難確定何時值得記住調用,並且很容易通過這樣做來消耗所有內存。 幸運的是,有幾個memoizing庫可以用來在適當的時候添加memoization。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.