views:

66

answers:

3

I want to write:

(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.)
                       (when user (.set_Credentials (NetworkCredential. user password ""))))]
    (.DownloadString client url)))

So I want to set the credentials only when they are given as an argument to the function. However it doesn't seem to work that way - and neither does it when I replace the when with an if.

The function works fine if I remove the when altogether.

I guess I can't use a when in a doto - are there nice ways to write this?

+3  A: 

I would just write it as:

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

with-open doesn't impose any particular demands on client, except that it have a no-arguments close method, so you do not need to "finish" it in any sense within the bindings vector.

dreish
I assumed from the way it was written that the credentials needed to be set before the connect is opened. Your code opens first, then sets credentials. I don't know if that's a problem or not.
Brian Carper
No, with-open does not open anything. It *only* guarantees that the bound variables are (.close)d when control flow leaves the with-open form, whether by exception or normal return.
dreish
Yeah, you're right. My mistake.
Brian Carper
+4  A: 

(Note: This should all hopefully work, but I cannot test it at this time. Please give it your own sanity check.)

You could write

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

That seems pretty convoluted to me, though. Another approach:

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

The convoluted -> / ->> pattern from the first version could be abstracted away with a macro:

(defmacro doto-guard [guard action]
  `(-> ~action ~guard))

Then you could write

(doto (WebClient.)
  (doto-guard (when user) (.setCredentials ...)))

This has the nice property that you could use it multiple times in a single doto form while mixing in regular doto clauses. Well, it's nice if this sort of thing comes up more often in your code, anyway. Otherwise the let-based version should do fine.

(If that pattern comes up really often for you, the macro could be made more flexible... It's also tempting to make it slightly less flexible, but prettier, say by replacing ~guard with (when ~guard), so that at point of use one would write (doto-guard user (.setCredentials ...)). Any deep reason to choose a particular version would have to come from a broader context, however.)

The split into two function bodies is just a matter of style -- I prefer not to write the nil nil when no credentials are actually provided.

Michał Marczyk
This answers Kurt's literal question, but he is asking the wrong question. Simpler code can accomplish the same thing.
dreish
Although I resent the "wrong question" bit somewhat, dreish is right. +1 for suggesting the overload without user and password.
Kurt Schelfthout
Sorry, I didn't mean to offend either of you. I ask wrong questions all the time. I'm pretty sure everyone does.
dreish
@dreish: No offense taken. I found it interesting to approach the general problem of performing parts of `doto` forms conditionally (while hopefully avoiding weird, unnatural macros), but upon a second look your answer definitely seems to provide the cleanest solution to the actual problem at hand. +1 for that. @Kurt: Sure. Happy to be of some help.
Michał Marczyk
+1  A: 
(defn download-web-page
  "Downloads the webpage at the given url and returns its contents."
  [^String url ^String user ^String password]
  (let [client (WebClient.)]
    (when user 
      (.set_Credentials client (NetworkCredential. user password "")))
    (with-open [client client]
      (.DownloadString client url)))

The (with-open [client client]... looks a bit weird, but hey, it's a nasty bit of stateful code. If that conditional setting in doto comes up frequently enough, it may warrant a macro, but I would jump to a builder function first:

(defn build-web-client
  [^String user ^String password]
  (let [client (WebClient.)]
    (when user 
      (.set_Credentials client (NetworkCredential. user password "")))
    client))

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