2016-07-21 15 views
3

Ich versuche, ein webscraper mit Pipes zu schreiben, und ich habe von folgenden geschabt Links zum Teil kommen. Ich habe eine process Funktion, die eine URL herunterlädt, findet Verbindungen und gibt sie zurück.Haskell Pipes - ein Rohr mit konsumieren, was es ergibt sich (selbst)

Jetzt möchte ich, wie rekursiv folgen diese Links, Threading der StateT durch. Mir ist klar, dass es wahrscheinlich etwas idiomatischeres gibt, als eine einzelne Röhre für den Großteil des Scraper zu verwenden (besonders wenn ich anfange, weitere Features hinzuzufügen), ich bin offen für Vorschläge. Ich werde wahrscheinlich das Design überdenken müssen, wenn ich Multithreading mit Shared State sowieso in Betracht ziehe.

+1

Egal, aber ich hätte wahrscheinlich 'Prozess :: (MonadState CState m, MonadIO m) => Pipe Item Item m()'.Nicht viel Code ändert (wahrscheinlich), lesbarer und abstrahiert die Implementierungsdetails Ihres Monadenstapels. – Alec

Antwort

4

Sie können einen Pipe a b m r an einen Nebeneffekt über den Parameter m anschließen, der austauscht, welche Monad die Leitung über arbeitet. Sie können dies verwenden, um Verbindungen erneut zu aktivieren, indem Sie das Downstream-Ende Ihrer Pipe mit einer anderen Pipe verbinden, die die Links in einer Warteschlange festhält und das Upstream-Ende Ihrer Pipe mit einer Pipe verbindet, die Links aus der Warteschlange liest.

Unser Ziel ist es

import Pipes 

loopLeft :: Monad m => Pipe (Either l a) (Either l b) m r -> Pipe a b m r 

schreiben Wir nehmen ein Rohr, dessen Downstream-Ausgang, Either l b, ist entweder ein Left l zurück stromaufwärts zu senden oder eine Right b stromabwärts, zu senden und die l senden s zurück in der stromaufwärtige Eingang Either l a, die entweder ein oder ein Left lRight a aus Richtung stromauf Warteschlange gestellt. Wir verbinden die Left l s zusammen ein Rohr zu machen, die a s von Upstream nur kommen sieht und liefert nur b s im Downstream geleitet.

Am unteren Ende werden wir die l s von Left l auf einen Stapel schieben. Wir yield die r s von Right r Downstream.

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) 

am oberen Ende etwas zu yield oben auf dem Stapel sehen wir. Wenn es keinen gibt, werden wir await für einen Wert von stromaufwärts und yield es.

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) 

Jetzt können wir loopLeft schreiben. Wir komponieren die vor- und nachgelagerten Rohre zusammen mit der Rohrzusammensetzung popLeft >-> hoist lift p >-> pushLeft. Die hoist lift wandelt eine Pipe a b m r in eine Pipe a b (t m) r um. Die distribute wandelt eine Pipe a b (t m) r in eine t (Pipe a b m) r um. Um zu einem Pipe a b m r zurück zu kommen, führen wir die gesamte StateT Berechnung beginnend mit einem leeren Stapel [] aus. In Pipes.Lift gibt es einen schönen Namen evalStateP für die Kombination von evalStateT und 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 
3

Ich würde es tun, wie folgt:

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') 

Sie können DFS oder BFS erreichen, je nachdem, wie Sie url' mit urls kombinieren.