【发布时间】:2018-12-03 10:57:35
【问题描述】:
意图: 学习 Haskell 的小应用程序:下载 wikipedia-article,然后下载其中链接的所有文章,然后下载链接的所有文章,依此类推……直到指定的递归深度达到。结果保存到文件中。
方法:使用StateT 跟踪下载队列、下载文章和更新队列。我递归地建立一个列表IO [WArticle],然后打印它。
问题:在进行分析时,我发现使用的总内存与下载的文章数量成正比。
分析:根据文献,我相信这是一个懒惰和/或严格的问题。 BangPatterns 减少了内存消耗,但没有解决比例问题。此外,我知道所有文章都是在文件输出开始之前下载的。
可能的解决方案:
1) 函数getNextNode :: StateT CrawlState IO WArticle(下)已经有IO。一种解决方案是只在其中写入文件并仅返回状态。这意味着文件被写入非常小的块。感觉不是很 Haskell..
2) 让函数buildHelper :: CrawlState -> IO [WArticle](下)返回[IO WArticle]。虽然我不知道如何重写该代码,并且在 cmets 中被建议不要使用它。
这些提议的解决方案是否比我认为的更好,或者是否有更好的替代方案?
import GetArticle (WArticle, getArticle, wa_links, wiki2File) -- my own
type URL = Text
data CrawlState =
CrawlState ![URL] ![(URL, Int)]
-- [Completed] [(Queue, depth)]
-- Called by user
buildDB :: URL -> Int -> IO [WArticle]
buildDB startURL recursionDepth = buildHelper cs
where cs = CrawlState [] [(startURL, recursionDepth)]
-- Builds list recursively
buildHelper :: CrawlState -> IO [WArticle]
buildHelper !cs@(CrawlState _ queue) = {-# SCC "buildHelper" #-}
if null queue
then return []
else do
(!article, !cs') <- runStateT getNextNode cs
rest <- buildHelper cs'
return (article:rest)
-- State manipulation
getNextNode :: StateT CrawlState IO WArticle
getNextNode = {-# SCC "getNextNode" #-} do
CrawlState !parsed !queue@( (url, depth):queueTail ) <- get
article <- liftIO $ getArticle url
put $ CrawlState (url:parsed) (queueTail++ ( if depth > 1
then let !newUrls = wa_links article \\ parsed
!newUrls' = newUrls \\ map fst queue
in zip newUrls' (repeat (depth-1))
else []))
return article
startUrl = pack "https://en.wikipedia.org/wiki/Haskell_(programming_language)"
recursionDepth = 3
main :: IO ()
main = {-# SCC "DbMain" #-}
buildDB startUrl recursionDepth
>>= return . wiki2File
>>= writeFile "savedArticles.txt"
完整代码https://gitlab.com/mattias.br/sillyWikipediaSpider。当前版本仅限于下载每个页面的前八个链接以节省时间。在不改变它的情况下,以大约 600 MB 的堆使用量下载 55 个页面。
感谢您的帮助!
【问题讨论】:
-
您的假设是不正确的:
IO [WArticle]在评估列表的每个元素时仍然很懒惰。IO不强制要求严格。这方面的一个例子是执行代码do {a <- return [undefined,1]; print (a !! 1)}。长话短说,使用[IO Article]s 只会让自己的生活更加艰难。 -
不是一个直接的答案,但conduit 和类似的库恰恰提供了该功能:处理具有更明确的资源控制的大型数据“列表”。
-
@AJFarmar,谢谢你的回答。所以这个问题应该改写为“为什么懒惰被打破了?”?由于
do {db <- buildDB url 3}打印出跟踪消息以立即下载所有文章(跟踪消息打印在getArticle中,此处未引用)。
标签: haskell lazy-evaluation state-monad io-monad