code-reviews

Bravi 2018-04-16T14:39:30.000912Z

Hi everyone. I’ve created a little script that crawls a specific website and downloads all of the mp3 files there. It’s a Georgian site, and only Georgians would understand this 🙂 But I wanted to perhaps get some suggestions code-wise. It all works, but there’s always a room for improvement right?

(ns play-around.core
  (:require [net.cgrand.enlive-html :as html]
            [org.httpkit.client :as http]
            [<http://clojure.java.io|clojure.java.io> :as io]
            [clojure.string :as string])
  (:gen-class))

(def urls #{"<http://radio105.wanex.net/arts_geo.htm>" "<http://radio105.wanex.net/songs_geo.htm>"})

(defn- get-dom
  [url]
  (html/html-snippet (:body @(http/get url))))

(defn- build-urls
  [audio-links]
  (for [audio-link audio-links]
    {:download-url (format "<http://radio105.wanex.net/%s>" audio-link)
     :filename audio-link}))

(defn- extract-links
  [dom]
  (map #(get-in % [:attrs :href]) (html/select dom [:td :a])))

(defn- download-file
  [{:keys [download-url filename] :as file}]
  (let [filename (string/replace filename #"\/" "_")
        file (io/file "audio" filename)]
    (if-not (.exists file)
      (do
        (println "Downloading" filename)
        (with-open [in (io/input-stream download-url)
                    out (io/output-stream file)]
          (io/copy in out)))
      (println "File" filename "already exists, skipping"))))

(defn- download-files
  [files]
  (doall (map download-file files)))

(defn- is-mp3?
  [link]
  (re-matches #"(?i).*\.mp3" link))

(defn -main
  [&amp; args]
  (when-not (.isDirectory (io/file "audio"))
    (.mkdir (io/file "audio")))
  (-&gt;&gt; urls
       (mapcat get-dom)
       extract-links
       (filter is-mp3?)
       build-urls
       download-files))

2018-04-16T14:51:37.000935Z

@bravilogy My 2 cents, build-urls should be a map, no ? May be you should rewrite the main like

(-&gt;&gt;
    urls
   (mapcat get-dom)
   (map ...)
   (filter ...)
   (map ...)
  ...
)
May be this will help you to see patterns, opportunity for transducers,...

Bravi 2018-04-16T14:52:41.000865Z

oh good point

Bravi 2018-04-16T14:55:35.000515Z

the thing about the last map is that I need to run it through doall somehow

Bravi 2018-04-16T14:55:43.000002Z

otherwise it’s not being ‘realized’

Bravi 2018-04-16T14:56:12.000477Z

so should I chuck doall in -&gt;&gt; after everything?

Bravi 2018-04-16T14:57:04.000584Z

or perhaps the thread’s output should then be passed to doall separately like

(doall (-&gt;&gt; urls
   (mapcat ...)
   ....))

Bravi 2018-04-16T14:57:23.000966Z

or even have it in a let block

Bravi 2018-04-16T14:58:54.000571Z

(defn -main
  [&amp; args]
  (when-not (.isDirectory (io/file "audio"))
    (.mkdir (io/file "audio")))
  (let [downloads (-&gt;&gt; urls
                       (mapcat get-dom)
                       extract-links
                       (filter is-mp3?)
                       (map build-url)
                       (map download-file))]
    (doall downloads)))

2018-04-16T15:00:39.000241Z

My Clojure experience is very limited, so take my advices with caution. 🙂 I think you should finish you (-&gt;&gt;) with a download-files, but all previous are map and filter, and as reader, I would appreciate to identify it quickly.

ghadi 2018-04-16T15:11:39.000404Z

be wary of functions that simply map over a collection like build-urls @bravilogy

ajs 2018-04-16T15:21:00.000122Z

with this you are trying to do side effects (doall (map download-file files))) but really doall/map is not a good combo for that. just use a doseq

2018-04-16T15:34:42.000202Z

Could you explain the point ?

ghadi 2018-04-16T15:36:49.000307Z

sure, if you have a function that takes a collection but does nothing but call (map (fn something ]) coll), what you want instead is to (defn something ...) and inline the map something part at the usage site

2018-04-16T15:39:32.000317Z

OK, I prefer this kind of code style, too.

ghadi 2018-04-16T15:42:51.000406Z

yeah it encourages reuse and idiomatic core library usage

👍 1
Bravi 2018-04-16T15:44:00.000194Z

(defn- download-files
  [files]
  (doseq [file files]
    (download-file file)))

(defn -main
  [&amp; args]
  (when-not (.isDirectory (io/file "audio"))
    (.mkdir (io/file "audio")))
  (-&gt;&gt; urls
       (mapcat get-dom)
       extract-links
       (filter is-mp3?)
       (map build-url)
       download-files))

Bravi 2018-04-16T15:45:19.000206Z

thanks everyone 🙂

2018-04-16T20:13:26.000314Z

@bravilogy There's also run! which is to map as doseq is to for. In this case I'd prefer (-&gt;&gt; ,,, ,,, (run! download-file)).

👍 1