2010-02-21 11 views
19

मैं ओओपी के वर्षों के बाद हास्केल सीख रहा हूं।हास्केल में राज्य के साथ "वेब मकड़ी" कैसे डिजाइन करें?

मैं कुछ कार्यों और राज्य के साथ एक गूंगा वेब मकड़ी लिख रहा हूं।
मुझे यकीन नहीं है कि एफपी दुनिया में इसे कैसे किया जाए।

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

प्रश्न: बताएंगे कि कैसे आप scracth से हास्केल में ऐसी बात डिजाइन होगा (ब्राउज़र कई स्वतंत्र उदाहरण होने की संभावना के साथ एपीआई के साथ)? कृपया, कोड स्निपेट दें।

नोट: सादगी के लिए, getLinks() फ़ंक्शन (इसके छोटे और दिलचस्प नहीं) पर विवरण छोड़ें।
भी मान लेते हैं एक API समारोह

getUrlContents :: String -> IO String 

HTTP कनेक्शन को खोलता है और इस URL का एक HTML देता है कि वहाँ करते हैं।


अद्यतन: क्यों राज्य के लिए (या नहीं हो सकता है)?

एपीआई में केवल "लोड-एंड-पार्स परिणाम" नहीं बल्कि अधिक फ़ंक्शन हो सकते हैं।
मैंने जटिलता से बचने के लिए उन्हें नहीं जोड़ा।

इसके अलावा यह वास्तविक ब्राउज़र व्यवहार को अनुकरण करने के लिए प्रत्येक अनुरोध के साथ HTTP रेफरर हेडर और कुकीज़ के बारे में भी ध्यान रख सकता है। पहले इनपुट क्षेत्र

  • क्लिक बटन "गूगल खोज"
  • लिंक पर क्लिक करें "2" लिंक
  • क्लिक में

    1. ओपन http://www.google.com/
    2. प्रकार "हास्केल":

      इस परिदृश्य पर विचार करें "3"

    3. वर्तमान पृष्ठ का HTML प्रिंट करें ("हैकेल" के लिए 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 रेफरर हेडर और कुकीज़ भेज सकता है जबकि सभी मैकेनिक्स को अपने अंदर छुपाकर और अच्छा एपीआई दे रहा है।

    यदि ब्राउज़र का कोई राज्य नहीं है, तो डेवलपर सभी मौजूदा यूआरएल/एचटीएमएल/कुकीज़ के आसपास गुजरने की संभावना है - और यह परिदृश्य कोड में शोर जोड़ता है।

    नोट: मुझे लगता है कि हैस्केल में एचटीएमएल को छीनने के लिए पुस्तकालयों के बाहर हैं, लेकिन मेरा इरादा एचटीएमएल को स्क्रैप नहीं करना था, लेकिन सीखें कि कैसे इन "ब्लैक बॉक्सिंग" चीजें हास्केल में ठीक से डिजाइन की जा सकती हैं।

  • उत्तर

    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) 
    

    अद्यतन (आपके अपडेट का उत्तर):

    तो ब्राउज़र एक राज्य है, यह HTTP Referer हेडर और कुकी भेज सकते हैं, जबकि सभी छुपा खुद के अंदर यांत्रिकी और अच्छा एपीआई दे रहा है।

    अभी भी राज्य की कोई आवश्यकता नहीं है, 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

    +1

    ध्यान दें कि ब्राउज़रएक्शन मोनैड पहले से मौजूद है: http://hackage.haskell.org/packages/archive/HTTP/4000.0.8/doc/html/Network-Browser.html – jrockway

    +1

    यह भी ध्यान दें कि 'फ़्लिप मानचित्र एम' 'forM' कहा जाता है। – BMeph

    3

    कई ऑब्जेक्ट-ओरिएंटेशन को दोहराने का प्रयास न करें।

    बस एक साधारण Browser का उपयोग और संशोधन सुविधा प्रदान करने के प्रकार है कि (अस्थिरता की खातिर IORef प्रति) वर्तमान URL रखती है और कुछ 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 :: स्ट्रिंग -> स्ट्रिंग -> [स्ट्रिंग]' की आवश्यकता नहीं है? – sth

    +1

    आईएमएचओ, यहां तक ​​कि आप ओओपी को दोहराने की कोशिश कर रहे हैं।इस कार्य के लिए, उत्परिवर्तन के लिए एकमात्र दूरस्थ रूप से संभावित लाभ एचटीएमएल और लिंक सूची को कैशिंग कर रहा है, जो आपका उत्तर नहीं करता है। और यहां तक ​​कि इसकी जरूरत नहीं है। –

    3

    getUrlContents समारोह पहले से ही goto() और getHtml() क्या करेंगे, केवल एक चीज याद आ रही एक समारोह है कि डाउनलोड की पृष्ठ से लिंक निकालता है करता है। यह एक स्ट्रिंग (किसी पृष्ठ का HTML) और एक यूआरएल ले सकता है (संबंधित लिंक्स को हल करने) और उस पृष्ठ से सभी लिंक निकालें:

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

    इन दोनों कार्यों से आप आसानी से अन्य कार्यों कि spidering कर निर्माण कर सकते हैं । उदाहरण के लिए "पहले लिंक किए गए पृष्ठ प्राप्त" उदाहरण ऐसा दिखाई दे सकता:

    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) 
    

    (ध्यान दें कि उदाहरण के लिए इस होगा अंत में लिंक में चक्रों का पालन करें - वास्तविक उपयोग के लिए एक समारोह को ऐसे मामलों का ख्याल रखना चाहिए)

    इन कार्यों द्वारा उपयोग किया जाने वाला केवल "राज्य" यूआरएल है और इसे केवल पैरामीटर के रूप में प्रासंगिक कार्यों को दिया जाता है।

    अगर वहाँ अधिक जानकारी सभी ब्राउज़िंग कार्यों की जरूरत है कि आप सभी को एक साथ समूह के लिए एक नए प्रकार बना सकते हैं होगा:

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

    कार्य है कि इस जानकारी का उपयोग तो बस इस प्रकार का एक पैरामीटर ले सकता है और निहित जानकारी का प्रयोग करें। इन वस्तुओं के कई उदाहरण होने और उन्हें एक साथ उपयोग करने में कोई समस्या नहीं है, प्रत्येक फ़ंक्शन केवल उस ऑब्जेक्ट का उपयोग करेगा जो इसे पैरामीटर के रूप में दिया जाता है।

    2

    दिखाएं कि आप हास्केल में स्क्रैच से ब्राउज़र की तरह एपीआई कैसे डिजाइन करेंगे (ब्राउज़र जैसी एपीआई कई स्वतंत्र उदाहरण होने की संभावना है)? कृपया, कोड स्निपेट दें।

    मैं प्रत्येक बिंदु पर एक (हास्केल) धागा का प्रयोग करेंगे, सभी धागे जो भी संसाधन वे जरूरत के रिकार्ड प्रकार के साथ राज्य इकाई में चल रहा है, और परिणाम एक चैनल पर मुख्य थ्रेड के लिए वापस भेजी है।

    और समेकन जोड़ें! वह एफपी तरीका है।

    अगर मैं सही ढंग से याद है, एक डिजाइन चैनलों पर संवाद स्थापित करने धागे की जाँच के लिंक के गिरोहों के लिए यहाँ नहीं है:

    इसके अलावा, तार, लेकिन उपयोग करने के लिए नहीं सुनिश्चित करें कि पाठ या ByteStrings - - वे बहुत तेज हो जाएगा।

    संबंधित मुद्दे