4

我有一個懶惰find創建的文件名列表。我希望能夠延遲加載這些文件的元數據。這意味着,如果我take 10元素從metadata,它應該只搜索這10個文件的元數據。事實上,find完美地給你10個文件,如果你問他們沒有掛你的磁盤,而我的腳本搜索所有文件的元數據。從非IO列表創建懶惰IO列表

main = do 
    files <- find always always/
    metadata <- loadMetaList files 

loadMetaList :: [String] -> IO [Metadata] 
loadMetaList file:files = do 
    first <- loadMeta file 
    rest <- loadMetaList files 
    return (first:rest) 

loadMeta :: String -> IO Metadata 

正如你所看到的,loadMetaList 更是不可以偷懶。因爲它是懶惰的,它應該使用尾遞歸。像return (first:loadMetaList rest)

我該如何製作loadMetaList lazy

+3

這是[pipes](http://hackage.haskell.org/package/pipes)或[conduit](http://hackage.haskell.org/package/conduit)的好用例。 – ErikR 2013-04-26 19:33:20

+0

起初我以爲這是「loadMetalList」,就像在一堆重金屬的mp3中一樣 – 2013-04-26 19:36:08

+0

其實是加載mp3的元數據 – 2013-04-26 19:57:08

回答

9

IO單子的(>>=)是使得在

loadMetaList :: [String] -> IO [Metadata] 
loadMetaList file:files = do 
    first <- loadMeta file 
    rest <- loadMetaList files 
    return (first:rest) 

動作loadMetaList files具有之前可以執行return (first:rest)中運行。

您可以避免通過推遲的loadMetaList files執行,

import System.IO.Unsafe 

loadMetaList :: [String] -> IO [Metadata] 
loadMetaList file:files = do 
    first <- loadMeta file 
    rest <- unsafeInterleaveIO $ loadMetaList files 
    return (first:rest) 

unsafeInterleaveIO(其中find也使用)。這樣,loadMetaList files直到需要它的結果纔會執行,並且如果您只需要10個文件的元數據,則只會加載該文件。

這不像它的表弟unsafePerformIO那樣不安全,但也應該小心處理。

+0

請問unsafeInterleaveIO何時「不安全」?爲什麼在我的情況下是安全的? – 2013-04-26 19:58:23

+3

你的情況並不一定安全。但是由於'find'已經使用它,所以放入'loadMetaList'不會增加不安全性,所以在這裏不需要太擔心。 'unsafeInterleaveIO'推遲執行它的參數,所以a)你失去了事情完成順序的可預測性,b)文件可以在延遲執行時被改變,所以你可能會得到與你得到的結果不同的結果沒有它,c)它可能會保持文件打開的時間比預期長,從而使你用完文件句柄,d)我確信還有其他潛在的問題。 – 2013-04-26 20:07:24

+0

但是,在a)不重要的情況下,b)和c)被認爲不是問題,那麼它會大大簡化很多事情。大多數普通的應用程序屬於這一類,從我的有限經驗來看,但對於那些不那麼可能是災難性的。 – 2013-04-26 20:09:37

8

以下是您如何操作pipes的方法。我真的不知道你是怎麼實現loadMetafind,所以我做的東西了:

import Pipes 

find :: Producer FilePath IO() 
find = each ["heavy.mp3", "metal.mp3"] 

type MetaData = String 

loadMeta :: String -> IO MetaData 
loadMeta file = return $ "This song is " ++ takeWhile (/= '.') file 

loadMetaList :: Pipe FilePath MetaData IO r 
loadMetaList = mapM loadMeta 

要運行它,我們只是撰寫處理階段就像一個管道和使用runEffect運行管道:

>>> runEffect $ find >-> loadMetaList >-> stdoutLn 
This song is heavy 
This song is metal 

有幾個關鍵的事情指出:

  • 您可以find一個Producer,這樣它也只是懶懶的搜索目錄樹。我知道你不需要這個功能,因爲你的文件集現在很小,但是當你的目錄變大時很容易包含。

  • 這很懶,但沒有unsafeInterleaveIO。它立即生成每個輸出並且不會等待首先收集整個結果列表。

例如,它會工作,即使我們用文件的無限名單:

>>> import qualified Pipes.Prelude as Pipes 
>>> runEffect $ each (cycle ["heavy.mp3", "metal.mp3"]) >-> loadMetaList >-> Pipes.stdoutLn 
This song is heavy 
This song is metal 
This song is heavy 
This song is metal 
This song is heavy 
This song is metal 
... 
  • 它只會計算儘可能必要的。如果我們指定我們只需要三個結果,即使我們提供了無限的文件列表,它也會執行返回兩個結果所需的最小加載量。

例如,我們用take罐蓋結果的數量:

>>> runEffect $ each (cycle ["heavy.mp3", "metal.mp3"]) >-> loadMetaList >-> Pipes.take 3 >-> Pipes.stdoutLn 
This song is heavy 
This song is metal 
This song is heavy 

所以,你問什麼是錯unsafeInterleaveIO。的unsafeInterleaveIO主要的限制是,你不能保證當IO行爲實際發生,這導致了以下常見的陷阱:

  • Handle s的讀取文件之前

  • IO發生的動作被意外關閉晚婚或不

  • 具有純代碼的副作用和投擲IOException小號

Haskell的IO系統與其他語言相比最大的優點是Haskell完全將評估模型與副作用順序解耦。當你使用懶惰IO時,你失去了解耦,然後副作用的順序與Haskell的評估模型緊密結合,這是一個倒退的巨大步驟。

這就是爲什麼使用懶惰IO通常不明智的原因,特別是現在有簡單而優雅的替代品。

如果你想了解更多關於如何使用pipes安全地實現懶惰IO,那麼你可以閱讀廣泛的pipes tutorial

+0

這太棒了。 – 2013-09-11 15:29:01