2010-09-01 6 views
6

Je suis à la recherche d'une façon plus idiomatiques, si possible, d'écrire le code clojure suivant:façon idiomatiques d'écrire la fonction .NET Interop

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

C'est ClojureCLR et travaille. (Le fait que c'est la variante CLR n'a pas beaucoup d'importance)

J'aimerais se débarrasser des defs (remplacer par permet? Peuvent-ils se référer les uns aux autres?)

Que diriez-vous d'une meilleure façon pour se rendre au courant - en gardant à l'esprit que ... le chaînage ne fonctionnera pas parce que j'ai besoin de fermer les flux plus tard.

EDIT: Après la réponse, j'ai trouvé un moyen beaucoup plus facile dans .NET de télécharger une page Web en utilisant la classe WebClient. Je encore utilisé plusieurs des approches recommandées de Michal - voulais juste enregistrer ce que je pense maintenant être la meilleure réponse:

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

Répondre

6

Le code de la question peut être réécrite assez idiomatiques comme si (modulo les fautes de frappe - espérant il n'y en a pas):

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

en supposant que la version .NET de with-open appels .Close les objets liés (comme je l'attends qu'il pourrait, mais ne sera pas en mesure de vérifier - pas REPL .NET à portée de main) et que .readToEnd consomme avidement le flux entier, cela pourrait être encore simplifié à

Mise à jour: juste vérifié que with-open de ClojureCLR appels .Dispose sur les objets liés. Si c'est OK à la place de .Close, génial; si .Close est nécessaire, vous pouvez écrire votre propre version de with-open à utiliser au lieu .Close (peut-être copier la plupart des 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)))) 

Quelques commentaires:

  1. Ne pas utiliser def, defn etc. n'importe où, sauf au niveau supérieur, sauf si vous savez vraiment que vous devez le faire. (En fait, les utiliser immédiatement à l'intérieur d'un haut niveau let est parfois utile si vous avez besoin de l'objet créé pour fermer au cours des let habitants liØes ... Rien de plus génial que devrait recevoir très un examen attentif!)

    def & Co. créer de haut niveau Vars ou réinitialiser leurs liaisons racines; le faire dans le cadre d'un fonctionnement régulier d'un programme est totalement contraire à l'esprit fonctionnel de Clojure. Peut-être plus important encore d'un point de vue pratique, toute fonction qui repose sur "posséder" un tas de Vars ne peut être exécutée par un fil à la fois; il n'y a aucune raison pour laquelle download-web-page devrait être ainsi limitée.

  2. let -les liaisons introduites peuvent ne pas être mutuellement récursives; les liaisons ultérieures peuvent faire référence à des liaisons antérieures, mais pas l'inverse. Des fonctions locales mutuellement récursives peuvent être introduites avec letfn; D'autres types d'objets mutuellement récursifs peuvent être un peu moins pratiques à créer en dehors du niveau supérieur (bien que cela ne soit pas impossible). Le code de la question ne repose pas sur des valeurs mutuellement récursives, donc let fonctionne très bien.