2010-09-01 43 views
6

我正在尋找一種更地道的方式,如果可能的話,習慣的方法來寫下面的Clojure代碼:編寫.NET互操作功能

(import '(System.Net HttpWebRequest NetworkCredential) 
     '(System.IO StreamReader)) 

(defn downloadWebPage 
    "Downloads the webpage at the given url and returns its contents." 
    [^String url ^String user ^String password] 
    (def req (HttpWebRequest/Create url)) 
    (.set_Credentials req (NetworkCredential. user password "")) 
    (.set_UserAgent req ".NET") 
    (def res (.GetResponse req)) 
    (def responsestr (.GetResponseStream res)) 
    (def rdr (StreamReader. responsestr)) 
    (def content (.ReadToEnd rdr)) 
    (.Close rdr) 
    (.Close responsestr) 
    (.Close res) 
    content 
) 

這是ClojureCLR和作品。 (事實上​​,它的CLR變種沒多大關係)

我想擺脫DEFS的(通過更換讓?他們可以稱呼對方?)

如何更好的方法要注意流 - 請記住,鏈接不起作用,因爲我需要稍後關閉流。

編輯:在答案後,我發現在.NET中使用WebClient類下載網頁的簡單方法。我還使用了許多的米哈爾商推薦的方法 - 只是想記錄一下我現在認爲是最好的答案:

(defn download-web-page 
    "Downloads the webpage at the given url and returns its contents." 
    [^String url ^String user ^String password] 
    (with-open [client (doto (WebClient.) 
         (.set_Credentials (NetworkCredential. user password "")))] 
     (.DownloadString client url))) 

回答

6

從問題的代碼可以相當地道的重寫,像這樣(模拼寫錯誤 - 這裏的希望是沒有的):

(defn download-web-page 
    "Downloads the webpage at the given url and returns its contents." 
    [^String url ^String user ^String password] 
    (let [req (doto (HttpWebRequest/Create url) 
       (.set_Credentials (NetworkCredential. user password "")) 
       (.set_UserAgent ".NET")) 
     response  (.GetResponse req) 
     response-stream (.GetResponseStream res) 
     rdr    (StreamReader. response-stream) 
     content (.ReadToEnd rdr)] 
    (.Close rdr) 
    (.Close response-stream) 
    (.Close response) 
    content)) 

假設 with-open電話 .Close在綁定對象的.NET版本(因爲我想到它可能,但不會是能夠檢查 - 手頭沒有.NET REPL)並且 .readToEnd急切地消耗整個流,這可以進一步簡化爲

更新:剛剛檢查ClojureCLR的with-open在綁定的對象上調用.Dispose。如果這可以代替.Close,那很好;如果需要.Close,你可以寫你自己的版本的with-open使用.Close代替(可能複製大多數the original):

(defn download-web-page 
    "Downloads the webpage at the given url and returns its contents." 
    [^String url ^String user ^String password] 
    (let [req (doto (HttpWebRequest/Create url) 
       (.set_Credentials (NetworkCredential. user password "")) 
       (.set_UserAgent ".NET"))] 
    (with-open [response  (.GetResponse req) 
       response-stream (.GetResponseStream res) 
       rdr    (StreamReader. response-stream)] 
     (.ReadToEnd rdr)))) 

一些評論:

  1. 不要使用defdefn等除了頂級的任何地方,除非你真的知道你需要這樣做。 (實際使用的時候,立即頂級let裏面,如果你需要創建的對象來收,較let結合的當地人......什麼比這更時髦應接受非常仔細推敲是偶爾有用!)

    def & Co.創建頂級變量或重置它們的根綁定;在程序的正常運行過程中這樣做完全違背了Clojure的功能精神。也許更重要的是從一個實際的POV中,依賴於「擁有」一堆Vars的任何函數一次只能由一個線程執行;沒有理由爲什麼download-web-page應該因此受到限制。

  2. let-引入的綁定可能不是相互遞歸的;後來的綁定可能指的是更早的綁定,但不是其他方式。相互遞歸的本地函數可以引入letfn;其他類型的相互遞歸對象在頂層之外創建可能不太方便(儘管絕不可能)。該問題的代碼不依賴於相互遞歸的值,所以let工作正常。