2010-02-21 37 views
19

我在多年的面向對象方面學習Haskell。如何在Haskell中設計具有狀態的「web蜘蛛」?

我正在寫一個功能和狀態很少的啞蜘蛛。
我不知道如何在FP世界中做到這一點。

在OOP的世界這種蜘蛛可以這樣設計(用法):

Browser b = new Browser() 
b.goto(「http://www.google.com/」) 

String firstLink = b.getLinks()[0] 

b.goto(firstLink) 
print(b.getHtml()) 

此代碼加載http://www.google.com/,然後「點擊」的第一個環節,第二個頁面的加載內容,然後打印的內容。

class Browser { 
    goto(url: String) : void // loads HTML from given URL, blocking 
    getUrl() : String // returns current URL 
    getHtml() : String // returns current HTML 
    getLinks(): [String] // parses current HTML and returns a list of available links (URLs) 

    private _currentUrl:String 
    private _currentHtml:String 
} 

這是possbile有2或「瀏覽器」一次,有自己獨立的狀態:

Browser b1 = new Browser() 
Browser b2 = new Browser() 

b1.goto(「http://www.google.com/」) 
b2.goto(「http://www.stackoverflow.com/」) 

print(b1.getHtml()) 
print(b2.getHtml()) 

問題:表明你將如何在Haskell從scracth設計這樣的事情(瀏覽器類似的API可能有幾個獨立的實例)?請給出一個代碼片段。

注意:爲了簡單起見,跳過getLinks()函數的細節(它的微不足道和不感興趣)。
也讓我們假設有打開HTTP連接,並返回給定的URL的HTML API函數

getUrlContents :: String -> IO String 




UPDATE:爲什麼有狀態(或可能不)?

該API可以有更多的功能,而不僅僅是單一的「加載和解析結果」。
我沒有添加它們來避免複雜性。

此外,它可以通過向每個請求發送HTTP Referer頭和Cookie來模擬真實的瀏覽器行爲。

考慮以下情形:

  1. 打開http://www.google.com/
  2. 類型 「哈斯克爾」 爲第一輸入區域
  3. 點擊按鈕 「谷歌搜索」
  4. 點擊鏈接 「2」
  5. 點擊鏈接「3」
  6. 打印當前頁面的HTML(谷歌結果第3頁,用於「haskell」)

有動手這樣的情況下,我作爲一個開發者想轉讓其儘可能接近的代碼:

Browser b = new Browser() 
b.goto("http://www.google.com/") 
b.typeIntoInput(0, "haskell") 
b.clickButton("Google Search") // b.goto(b.finButton("Google Search")) 
b.clickLink("2") // b.goto(b.findLink("2")) 
b.clickLink("3") 
print(b.getHtml()) 

此方案的目的是後能得到最後一頁的HTML一組操作。 另一個不太明顯的目標是保持代碼緊湊。

如果瀏覽器有一個狀態,它可以發送HTTP Referer頭和cookie,同時隱藏所有機制並提供良好的API。

如果瀏覽器沒有狀態,開發人員可能會傳遞所有當前的URL/HTML/Cookies - 這會給場景代碼增加噪音。

注意:我猜Haskell中存在用於報廢HTML的庫,但我的目的不是要廢除HTML,而是要了解如何在Haskell中正確設計這些「黑盒子」的東西。

回答

12

正如您所描述的問題,沒有必要爲國家所有:

data Browser = Browser { getUrl :: String, getHtml :: String, getLinks :: [String]} 

getLinksFromHtml :: String -> [String] -- use Text.HTML.TagSoup, it should be lazy 

goto :: String -> IO Browser 
goto url = do 
      -- assume getUrlContents is lazy, like hGetContents 
      html <- getUrlContents url 
      let links = getLinksFromHtml html 
      return (Browser url html links) 

這是possbile有2或「瀏覽器」一次,有自己獨立的狀態:

你顯然可以擁有儘可能多的,而且他們不能互相干擾。

現在相當於你的片段。第一:

htmlFromGooglesFirstLink = do 
           b <- goto "http://www.google.com" 
           let firstLink = head (links b) 
           b2 <- goto firstLink -- note that a new browser is returned 
           putStr (getHtml b2) 

其次:

twoBrowsers = do 
       b1 <- goto "http://www.google.com" 
       b2 <- goto "http://www.stackoverflow.com/" 
       putStr (getHtml b1) 
       putStr (getHtml b2) 

UPDATE(回覆您的更新):

如果瀏覽器有一個狀態,它可以發送HTTP引用頭和餅乾,同時隱藏所有機制內部並提供良好的API。

不需要狀態,goto只需要一個瀏覽器參數。首先,我們需要擴展類型:

data Browser = Browser { getUrl :: String, getHtml :: String, getLinks :: [String], 
         getCookies :: Map String String } -- keys are URLs, values are cookie strings 

getUrlContents :: String -> String -> String -> IO String 
getUrlContents url referrer cookies = ... 

goto :: String -> Browser -> IO Browser 
goto url browser = let 
        referrer = getUrl browser 
        cookies = getCookies browser ! url 
        in 
        do 
        html <- getUrlContents url referrer cookies 
        let links = getLinksFromHtml html 
        return (Browser url html links) 

newBrowser :: Browser 
newBrowser = Browser "" "" [] empty 

如果瀏覽器沒有狀態,開發商很可能會繞過目前所有的URL/HTML /餅乾 - 這增加了噪聲場景代碼。

不,您只需傳遞類型瀏覽器的值。對於你的榜樣,

useGoogle :: IO() 
useGoogle = do 
       b <- goto "http://www.google.com/" newBrowser 
       let b2 = typeIntoInput 0 "haskell" b 
       b3 <- clickButton "Google Search" b2 
       ... 

或者你可以擺脫那些變量:

(>>~) = flip mapM -- use for binding pure functions 

useGoogle = goto "http://www.google.com/" newBrowser >>~ 
      typeIntoInput 0 "haskell" >>= 
      clickButton "Google Search" >>= 
      clickLink "2" >>= 
      clickLink "3" >>~ 
      getHtml >>= 
      putStr 

這是否看起來不夠好?請注意,瀏覽器仍然是不可變的。

+0

輝煌。 ... – oshyshko 2010-02-22 00:37:40

+1

請注意,BrowserAction monad已經存在:http://hackage.haskell.org/packages/archive/HTTP/4000.0.8/doc/html/Network-Browser.html – jrockway 2010-02-24 04:57:51

+1

另請注意,'flip mapM'被稱爲'forM'。 – BMeph 2010-07-12 05:56:32

3

不要試圖複製到許多面向對象。

只需定義一個簡單的Browser類型,其中包含當前URL(爲了可變性,每IORef)和一些IO函數提供訪問和修改功能。

樣本PROGRAMM應該是這樣的:

import Control.Monad 

do 
    b1 <- makeBrowser "google.com" 
    b2 <- makeBrowser "stackoverflow.com" 

    links <- getLinks b1 

    b1 `navigateTo` (head links) 

    print =<< getHtml b1 
    print =<< getHtml b2 

請注意,如果你定義像o # f = f o一個輔助函數,你將有一個更喜歡對象的語法(如b1#getLinks)。

完整的類型定義:

data Browser = Browser { currentUrl :: IORef String } 

makeBrowser :: String -> IO Browser 

navigateTo :: Browser -> String -> IO() 
getUrl  :: Browser -> IO String 
getHtml  :: Browser -> IO String 
getLinks  :: Browser -> IO [String] 
+3

爲什麼你想讓瀏覽器「對象」和模仿面向對象的設計/接口/語法?不需要簡單的附加'getLinks :: String - > String - > [String]'是否需要? – sth 2010-02-21 09:49:55

+1

恕我直言,即使你試圖複製OOP太多。對於這項任務,可變性唯一可能的遠程優勢是緩存HTML和鏈接列表,這是您的答案不能做到的。即使在那裏,它也不是必需的。 – 2010-02-21 21:19:52

3

getUrlContents功能已經做什麼goto()getHtml()會做,唯一缺少的是提取從下載頁面鏈接的功能。這可能需要一個字符串(頁面的HTML)和URL(解決相關鏈接),並提取該頁面所有鏈接:

getLinks :: String -> String -> [String] 

從這兩個功能,您可以輕鬆地構建做蜘蛛等功能。例如,「得到第一個鏈接的頁面」的例子看起來是這樣的:

getFirstLinked :: String -> IO String 
getFirstLinked url = 
    do page <- getUrlContents url 
     getUrlContents (head (getLinks page url)) 

一個簡單的功能,以下載的一切,從一個網址鏈接可能是:

allPages :: String -> IO [String] 
allPages url = 
    do page <- getUrlContent url 
     otherpages <- mapM getUrlContent (getLinks page url) 
     return (page : otherpages) 

(請注意,此例如將在循環中循環循環 - 實際使用的功能應該處理這種情況)

只有這些函數使用的「狀態」是URL,它只是作爲參數提供給相關函數。

如果將有更多的信息,所有的瀏覽功能需要,你可以以組創建一個新的類型一起:使用該信息,則可以簡單地採取這種類型的參數

data BrowseInfo = BrowseInfo 
    { getUrl  :: String 
    , getProxy :: ProxyInfo 
    , getMaxSize :: Int 
    } 

功能和使用包含的信息。有許多這些對象的實例並同時使用它們是沒有問題的,每個函數都會使用它作爲參數給出的對象。

2

顯示你將如何從scracth(類似瀏覽器的API,可能有幾個獨立實例)在Haskell中設計這樣的事情?請給出一個代碼片段。

我會用一個(哈斯克爾)線程在每一個點,都與一個記錄類型的,他們需要的任何資源國家單子運行的所有線程,並有結果反饋給主線程在一個通道。

添加更多併發性!這是FP方式。

如果我沒有記錯,這裏有一個設計用於檢查線程通信的渠道環節的團伙:

此外,還要確保不使用字符串,但文本或字節串 - - 他們會更快。