2010-05-06 28 views
10

我想要更深入地理解Haskell中的懶惰。懶惰和I/O如何在Haskell中一起工作?

今天

我想象下面的代碼片段:

data Image = Image { name :: String, pixels :: String } 

image :: String -> IO Image 
image path = Image path <$> readFile path 

這裏吸引人的是,我可以簡單地創建一個Image實例,並通過它;如果我需要將讀取的圖像數據懶洋洋地 - 如果不是,讀取文件的時間和內存成本將被避免:

main = do 
    image <- image "file" 
    putStrLn $ length $ pixels image 

不過是它如何工作?懶惰是如何與IO兼容的?無論我訪問pixels image是否會調用readFile,或者如果我從未參考它,運行時是否會將該Thunk評估爲未評估?

如果圖像確實是懶懶地讀取的,那麼不可能發生I/O操作失常嗎?例如,如果在撥打image後立即刪除該文件?現在putStrLn調用在嘗試讀取時什麼也找不到。

回答

17

懶惰與I/O如何兼容?

簡短回答:不是。


龍答:IO行動嚴格測序,對於幾乎你想的原因。當然,對結果所做的任何純粹的計算都可能是懶惰的。例如,如果您讀取文件,執行一些處理,然後打印出一些結果,則可能不會評估輸出不需要的任何處理。然而,整個文件將被讀取,甚至你永遠不會使用的部分。如果你想偷懶I/O,你有大致兩種選擇:

  • 推出自己的明確的延遲加載程序和這樣的,就像您在任何嚴格的語言。看起來很煩人,但是另一方面,Haskell制定了一個嚴格而嚴格的語言。如果你想嘗試一些新鮮有趣的東西,試試看Iteratees

  • 作弊作弊作弊。功能such as hGetContents將爲您做懶惰的按需I/O,不問任何問題。有什麼收穫?它(技術上)違反了參考透明度。純粹的代碼可以間接導致副作用,如果代碼真的很複雜,有趣的事情可能會發生,包括副作用的排序。 hGetContents和朋友們實現了using unsafeInterleaveIO,這正是它在錫上所說的。在使用unsafePerformIO時,你的臉上很可能會爆炸,但請注意自己的警告。

+1

感謝您的回答! 事實上,RWH對hGetContents的描述使我對這個問題感到困惑。我沒有意識到這是一個特殊情況,並在下面使用了不安全的IO調用。所以基本上,我的示例在readFile操作被處理後立即讀取文件?如果是這樣,這似乎更加一致。 – Bill 2010-05-06 01:36:01

+5

@Bill:這裏是readFile的實現,直接來自GHC的標準庫:'readFile name = openFile name ReadMode >> = hGetContents'所以不,你的例子屬於「作弊作弊者」類別。也就是說,懶惰的I/O功能對於大多數日常實際使用來說通常是足夠安全的,所以除非純度對您來說非常重要,否則不要過多地冒汗。 – 2010-05-06 01:50:27

+1

我知道奧列格說'unsafeInterleaveIO'打破了參照透明度,但我不同意。我會說這只是非確定性的,就像'IO'單元中的許多事情一樣。 'getCurrentTime'是否中斷參照透明度,因爲我可以使用它來確定兩個本質上相等的函數中的哪一個更有效地實現? – 2010-05-07 05:30:28

9

惰性I/O打破了Haskell的純度。根據需要,readFile的結果確實是懶惰地產生的。 I/O操作發生的順序不是固定的,所以是的,它們可能發生「亂序」。在拉動像素之前刪除文件的問題是真實的。簡而言之,懶惰I/O非常方便,但它是一個邊緣非常銳利的工具。

關於真實世界哈斯克爾的書有lengthy treatment of lazy I/O,並克服了一些陷阱。