Идиоматический способ написания функции взаимодействия .NET - PullRequest
6 голосов
/ 02 сентября 2010

Я ищу более идиоматический способ, если возможно, написать следующий код 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 не имеет большого значения)

Я хотел бы избавиться от определений (заменить на let? Могут ли они ссылаться друг на друга?)

Како лучшем способе добраться до потока - помните, что .. цепочка не будет работать, потому что мне нужно закрыть потоки позже.

РЕДАКТИРОВАТЬ: После ответа я нашел гораздо более простой способ.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)))

1 Ответ

6 голосов
/ 02 сентября 2010

Код вопроса может быть переписан довольно идиоматически, например, так (по модулю любые опечатки - здесь есть надежда, что их нет):

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

Предполагая, что .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))))

Некоторые комментарии:

  1. Не используйте def, defn и т. Д. Нигде, кроме как на верхнем уровне, если только вы действительно не знаете, что это нужно делать. (На самом деле их использование непосредственно внутри верхнего уровня let иногда полезно, если вам нужно, чтобы создаваемый объект закрывался над let локальными объектами ... Что-нибудь более забавное, чем это должно получить очень тщательное изучение!)

    def & Co. создают Vars верхнего уровня или сбрасывают их корневые привязки; выполнение этого в ходе обычной работы программы полностью противоречит функциональному духу Clojure. Возможно, что еще более важно из практического POV, любая функция, которая опирается на «владение» группой Vars, может выполняться только одним потоком за раз; нет никаких причин, по которым download-web-page должно быть таким образом ограничено.

  2. let - введенные привязки не могут быть взаимно рекурсивными; более поздние привязки могут относиться к более ранним привязкам, но не наоборот. Взаимно рекурсивные локальные функции могут быть введены с letfn; другие типы взаимно рекурсивных объектов могут быть несколько менее удобными для создания вне верхнего уровня (хотя ни в коем случае невозможно). Код из вопроса не основан на взаимно рекурсивных значениях, поэтому let отлично работает.

...