2010-02-21 4 views
19

나는 OOP의 년 후에 하스켈을 배우고있다.하스켈에서 "거미줄"을 어떻게 디자인 할 것인가?

저는 몇 가지 기능과 상태로 멍청한 웹 거미를 쓰고 있습니다.
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 
} 

그것은 자신의 분리 된 상태로, 한 번에 2 또는 "브라우저"를 가지고 possbile입니다 :

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

질문가 : 당신이 scracth에서 하스켈에서 그런 일을 설계 할 방법을 보여줍니다 (브라우저 여러 독립 인스턴스를 가질 수있는 유사한 API)? 코드 스 니펫을주세요.

참고 : 간결하게하기 위해 getLinks() 함수에 대한 설명은 생략하십시오.
또한 이제 HTTP 연결을 열고 지정된 URL에 대한 HTML을 반환하는 API 함수

getUrlContents :: String -> IO String 

이 있다고 가정하자.


UPDATE : 왜 국가가 (또는하지 않을 수 있습니다)?

API는 단일 "로드 및 구문 분석 결과"가 아닌 더 많은 기능을 가질 수 있습니다.
복잡성을 피하기 위해 추가하지 않았습니다.

또한 실제 브라우저 동작을 에뮬레이션하기 위해 각 요청과 함께 HTTP Referer 헤더 및 쿠키를 보내어 신경 쓸 수 있습니다. 첫번째 입력 영역

  • 클릭 버튼 "구글 검색"
  • 클릭 링크 "2"
  • 클릭 링크로

    1. 열기 http://www.google.com/
    2. 유형 "하스켈"

      다음과 같은 경우를 생각해 볼 수 "3"

    3. 현재 페이지의 HTML을 인쇄 ("haskell"에 대한 google 결과 페이지 3)

    손에이 같은 시나리오를 갖는, 개발자로서 나는 가능한 한 가까이 코드로 전송하고 싶습니다 :

    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 헤더와 쿠키를 보내고 내부의 모든 기능을 숨기고 멋진 API를 제공 할 수 있습니다.

    브라우저에 상태가없는 경우 개발자는 현재 URL/HTML/쿠키를 모두 지나칠 가능성이 있으며 시나리오 코드에 노이즈가 추가됩니다.

    참고 : 하스켈에서는 HTML을 스크랩하는 라이브러리가 있지만 내 의도는 HTML을 스크랩하지 않았지만 이러한 "블랙 박스"가 하스켈에서 어떻게 올바르게 디자인 될 수 있는지를 배우는 것입니다.

  • 답변

    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) 
    

    그것은, 한 번에 2 또는 "브라우저"를 가지고 possbile의 자신의 분리 된 상태 :

    분명히 원하는 수만큼 가질 수 있으며 서로 간섭 할 수 없습니다.

    이제 스 니펫과 동일합니다. 첫째 :

    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

    Brilliant. .... – oshyshko

    +1

    BrowserAction 모나드가 이미 있음을 참고하십시오 : http://hackage.haskell.org/packages/archive/HTTP/4000.0.8/doc/html/Network-Browser.html – jrockway

    +1

    또한 'flip mapM' 'forM'이라고합니다. – BMeph

    3

    많은 개체 지향에 복제하지 마십시오.

    그냥 액세스 및 수정 기능을 제공하는 간단한 (가변성을 위해 IORef 당) 현재의 URL을 보유 Browser 유형과 일부 IO 기능을 정의합니다.

    샘플을 programm는 다음과 같이 보일 것이다 : 당신이 o # f = f o 같은 도우미 함수를 정의하면, 좀 더 객체와 같은 구문 (예를 들어, b1#getLinks)를해야한다는

    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 
    

    참고.

    전체 유형 정의 :

    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

    +1

    IMHO, OOP를 너무 많이 복제하려는 경우조차도.이 작업을 수행 할 때 가능한 한 원격으로 가능한 변경 사항은 HTML 및 링크 목록 캐싱뿐입니다. 그리고 심지어 거기에 그것은 필요하지 않습니다. –

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

    간단한 함수가 될 수있는 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)에서 디자인 할 수 있을까요? 코드 스 니펫을주세요.

    나는 각 지점에서 하나 (하스켈) 스레드를 사용하는 것이, 그들은 무엇을해야 자원의 레코드 유형과 국가 모나드에서 실행중인 모든 스레드를 가지고 있고, 결과는 채널을 통해 메인 스레드로 다시 전달했다.

    더 많은 동시성 추가! 그것이 FP 방법입니다.

    은 내가 올바르게 기억 디자인은 채널을 통해 통신 스레드를 확인하는 링크의 갱단 여기있다 :

    또한, 문자열,하지만 사용하지 않는 확인 텍스트 나 ByteStrings - - 그들은 훨씬 빨라질 것입니다.

    관련 문제