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

Kur*_*out 6 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
  )
Run Code Online (Sandbox Code Playgroud)

这是在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)))
Run Code Online (Sandbox Code Playgroud)

Mic*_*zyk 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))
Run Code Online (Sandbox Code Playgroud)

假设绑定对象的.NET版本with-open调用.Close(正如我预期的那样,但无法检查 - 手头没有.NET REPL)并且.readToEnd急切地消耗整个流,这可以进一步简化为

更新:刚刚检查了ClojureCLR 对绑定对象的with-open调用.Dispose.如果可以代替.Close,那很好; 如果.Close需要,您可以编写自己的版本with-open.Close代替使用(可能复制大部分原文):

(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))))
Run Code Online (Sandbox Code Playgroud)

一些评论:

  1. 不要使用def,defn等等,除了在最顶层,除非你真的知道你需要做的任何地方.(实际上,let如果你需要创建的对象来关闭let当地人,那么立即在顶层使用它们偶尔会很有用......任何比这更加时髦的东西应该得到非常仔细的审查!)

    def&Co.创建顶级Vars或重置其根绑定; 在程序的常规操作过程中这样做完全违背了Clojure的功能精神.也许更重要的是,从实际的POV来看,任何依赖于"拥有"一堆Vars的函数一次只能由一个线程执行; 没有理由download-web-page应该这样限制.

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