2010-02-21 39 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方式。

如果我没有记错,这里有一个设计用于检查线程通信的渠道环节的团伙:

此外,还要确保不使用字符串,但文本或字节串 - - 他们会更快。