我正在尝试使用Pipes编写一个webscraper,而且我已经参与了以下抓取的链接。我有一个process
函数可以下载网址,查找链接并生成链接。
process :: Pipe Item Item (StateT CState IO) ()
....
for (each links) yield
....
现在我想了解一些如何以递归方式跟随这些链接,将StateT穿过。我意识到可能会做一些更惯用的事情,然后使用单个管道来处理大量的刮刀(尤其是当我开始添加更多功能时),我可以提出建议。当我考虑多线程w /共享状态时,我可能不得不重新考虑设计。
答案 0 :(得分:4)
您可以通过Pipe a b m r
参数将m
连接到副作用,该参数可以交换管道正在运行的Monad
。您可以通过将管道的下游端连接到另一个管道来将链路重新排队,该管道将链路粘贴在队列中,并将管道的上游端连接到从队列中读取链接的管道。
我们的目标是写
import Pipes
loopLeft :: Monad m => Pipe (Either l a) (Either l b) m r -> Pipe a b m r
我们将使用一个管道,其下游输出Either l b
要么是Left l
要发回上游要么是Right b
要发送到下游,要发送{{1回到上游输入l
,它是来自上游的排队Either l a
或Left l
。我们将Right a
连接在一起,制作一个只看到Left l
来自上游的管道,只会产生a
向下游。
在下游端,我们将b
从l
推到堆栈上。我们Left l
来自yield
下游的r
。
Right r
在上游端,我们会在堆栈顶部寻找import Control.Monad
import Control.Monad.Trans.State
pushLeft :: Monad m => Pipe (Either l a) a (StateT [l] m) r
pushLeft = forever $ do
o <- await
case o of
Right a -> yield a
Left l -> do
stack <- lift get
lift $ put (l : stack)
的内容。如果没有,我们会yield
获取上游的值和await
。
yield
现在我们可以写popLeft :: Monad m => Pipe a (Either l a) (StateT [l] m) r
popLeft = forever $ do
stack <- lift get
case stack of
[] -> await >>= yield . Right
(x : xs) -> do
lift $ put xs
yield (Left x)
了。我们将上游和下游管道与管道组成loopLeft
组合在一起。 popLeft >-> hoist lift p >-> pushLeft
将hoist lift
变为Pipe a b m r
。 distribute
将Pipe a b (t m) r
变为Pipe a b (t m) r
。要返回到t (Pipe a b m) r
,我们将以空堆栈Pipe a b m r
开始运行整个StateT
计算。在[]
中,Pipes.Lift
和evalStateT
的组合名称evalStateP
很好。
distribute
答案 1 :(得分:3)
我会这样做:
import Pipes
type Url = String
getLinks :: Url -> IO [Url]
getLinks = undefined
crawl :: MonadIO m => Pipe Url Url m a
crawl = loop []
where
loop [] = do url <- await; loop [url]
loop (url:urls) = do
yield url
urls' <- liftIO $ getLinks url
loop (urls ++ urls')
您可以根据url'
与urls
的合并方式实现DFS或BFS。