An idiomatic way to write a .NET interaction function

I am looking for a more idiomatic way, if possible, to write the following clojure code:

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

It works on ClojureCLR and it works. (the fact that this is a variant of the CLR doesn't really matter)

I would like to get rid of defs (replace with let? Can they refer to each other?)

What about a better way to get to the thread - bearing in mind that .. the chain is not working because I need to close the threads later.

EDIT: after the answer, I found a much simpler way in .NET to load a web page using the WebClient class. I still used many approaches recommended by Michal - I just wanted to write down what I consider to be the best answer:

 (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
clojure clojureclr
source share
1 answer

The code from the question can be rewritten quite idiomatically like this (modulo any typos - here hoping that they are not):

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

Assuming the .NET version of with-open calls .Close on related objects (I suppose it can, but cannot check if there is no .NET REPL at hand) and that .readToEnd eagerly consumes the whole thread, it could be more simplified to

Update: Just verified that ClojureCLR with-open calls .Dispose on related objects. If this is normal instead of .Close , excellent; if .Close is required, you can write your own version of with-open instead of .Close (possibly by copying most of 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)))) 

Some comments:

  • Do not use def , defn , etc. anywhere except the top level, if you really don't know what you need to do this. (In fact, using them directly inside the top level let sometimes useful if you need an object to create to close local let sites ... Something more ridiculous than this should get very careful monitoring!)

    def and Co. create upper levels of Vars or reset their root bindings; this occurs during the normal operation of the program, completely contrary to the functional spirit of Clojure. Perhaps more importantly, from a practical POV, any function that relies on “owning” a Vars bundle can only be executed one thread at a time; there is no reason why download-web-page should be so limited.

  • let - the entered bindings cannot be mutually recursive; later bindings may refer to earlier ties, but not vice versa. Mutually recursive local functions can be introduced using letfn ; other types of mutually recursive objects may be somewhat less convenient to create outside the top level (although by no means impossible). The code from the question does not depend on mutually recursive values, so let works fine.

+6
source share

All Articles