首页 文章

Haskell管道 - 管道消耗它产生的东西(本身)

提问于
浏览
3

我'm trying to write a webscraper using Pipes and I'已经到了以下抓链接的部分 . 我有一个 process 函数,可以下载网址,查找链接并生成链接 .

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

现在我想要一些如何递归地跟随这些链接,穿过StateT . 我意识到可能会做一些更惯用的事情,然后使用单个管道来处理大量的刮刀(特别是当我开始添加更多功能时),我愿意接受建议 . 当我考虑多线程w /共享状态时,我可能不得不重新考虑设计 .

2 回答

  • 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 .

  • 4

    您可以通过 m 参数将 Pipe a b m r 连接到副作用,该参数交换管道正在操作的 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 要向下游发送,然后将 l 发送回上行输入 Either l a ,这是排队的 Left lRight a 即将到来从上游 . 我们将 Left l 连接在一起,形成一个只能看到来自上游的 a 的管道,只会产生下行的 b .

    在下游端,我们将 lLeft 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 . 如果没有't one, we' ll 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 liftPipe a b m r 变成 Pipe a b (t m) r . distributePipe a b (t m) r 变成 t (Pipe a b m) r . 为了回到 Pipe a b m r ,我们以空堆栈 [] 开始运行整个 StateT 计算 . 在 Pipes.Lift 中,对于 evalStateTdistribute 的组合,有一个很好的名称evalStateP .

    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
    

相关问题