编写.NET互操作函数的惯用法

时间:2010-09-01 22:23:27

标签: clojure clojureclr

如果可能,我正在寻找一种更惯用的方法来编写以下clojure代码:

(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(替换为let?他们可以互相引用吗?)

如何更好地获取流 - 请记住..链接不起作用,因为我需要稍后关闭流。

编辑:在回答之后,我发现在.NET中使用WebClient类下载网页更容易。我仍然使用了许多Michal推荐的方法 - 只想记录我现在认为最好的答案:

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

1 个答案:

答案 0 :(得分: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的.NET版本在绑定对象上调用.Close(正如我预期的那样,但无法检查 - 手头没有.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&公司创建顶级Vars或重置他们的根绑定;在程序的常规操作过程中这样做完全违背了Clojure的功能精神。也许更重要的是,从实际的POV来看,任何依赖于“拥有”一堆Vars的函数一次只能由一个线程执行;因此download-web-page没有理由限制。

  2. let - 引入的绑定可能不是相互递归的;以后的绑定可能会引用早期的绑定,但不是相反。 letfn可以引入相互递归的局部函数;其他类型的相互递归对象在顶层之外创建可能不太方便(尽管绝不是不可能的)。问题中的代码不依赖于相互递归的值,因此let可以正常工作。