Haskell管道——让管道消耗它产生的(自身)

Haskell管道——让管道消耗它产生的(自身),haskell,pipe,haskell-pipes,Haskell,Pipe,Haskell Pipes,我正在尝试使用管道编写一个webscraper,我已经来到了下面的链接部分。我有一个过程函数,可以下载url、查找链接并生成它们 process :: Pipe Item Item (StateT CState IO) () .... for (each links) yield .... 现在我想了解一下如何递归地跟踪这些链接,并将StateT贯穿其中。我意识到有可能会做一些更为惯用的事情,然后在大部分刮板上使用单个管道(特别是当我开始添加更多功能时),我愿意听取建议。当我考虑多

我正在尝试使用管道编写一个webscraper,我已经来到了下面的链接部分。我有一个
过程
函数,可以下载url、查找链接并生成它们

process :: Pipe Item Item (StateT CState IO) ()
 ....
    for (each links) yield
 ....

现在我想了解一下如何递归地跟踪这些链接,并将StateT贯穿其中。我意识到有可能会做一些更为惯用的事情,然后在大部分刮板上使用单个管道(特别是当我开始添加更多功能时),我愿意听取建议。当我考虑多线程W/Stand状态时,我可能不得不重新考虑设计。

< P>我会这样做:

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=String
getLinks::Url->IO[Url]
getLinks=未定义
爬网::MonadIO m=>管道Url m a
爬网=循环[]
哪里

loop[]=do url您可以通过
m
参数将
管道a b m r
连接到副作用,该参数将管道运行的
Monad
替换掉。通过将管道的下游端连接到将链接粘贴到队列中的另一个管道,并将管道的上游端连接到从队列中读取链接的管道,可以使用此选项对链接进行重新排队

我们的目标是写作

import Pipes

loopLeft :: Monad m => Pipe (Either l a) (Either l b) m r -> Pipe a b m r
我们将取一个管道,其下游输出,
或Lb
,要么是一个
左l
向上游发送,要么是一个
右b
向下游发送,然后将
l
返回上游输入
或La
,这要么是一个排队的
左l
,要么是来自上游的
右a
。我们将把
左l
s连接在一起,形成一个管道,只看到
a
s来自上游,只产生
b
s流向下游

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)
在下游端,我们将把
l
s从
Left l
推到堆栈上。我们
右r
下游生产
r
s

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)
现在我们可以编写
loopLeft
。我们将上游和下游管道与管道组合一起构成
popLeft>->提升提升p>->pushLeft
提升装置将
管道a b m r
转换为
管道a b(t m)r
。将a
管道a b(TM)r
转换为a
t(管道a b m)r
。为了回到管道a b m r
,我们从空堆栈开始运行整个
StateT
计算
[]
。在
Pipes.Lift
中,
evalStateT
distribute
的组合有一个很好的名字

import Pipes.Lift

loopLeft :: Monad m => Pipe (Either l a) (Either l b) m r -> Pipe a b m r
loopLeft p = flip evalStateT [] . distribute $ popLeft >-> hoist lift p >-> pushLeft

偏好问题,但我可能有
流程::(MonadState CState m,MonadIO m)=>管道项目m()
。没有太多的代码更改(可能),可读性更高,并且抽象了monad堆栈的实现细节。