I'd try to separate the "I/O or side-effecting" parts from the "purely data processing" parts. This makes the program much easier to test --- the "purer" the code, the better it is. This also helps tease apart domain-agnostic parts from domain-specialised parts, which is useful, because domain-agnostic parts tend to be generalisable and thus more reusable.
I'd also avoid mixing lazy functions like `map` with effectful things like `GET` requests, because : https://stuartsierra.com/2015/08/25/clojure-donts-lazy-effects I've taken the liberty to rearrange the code, and rename functions to illustrate what I mean. (defn pipeline-api-endpoint ;; lifted out of `fetch-pipeline` [project-name] ;; knows how to translate, or perhaps more generally, map, a project name to a project URL format (str "https://example.com/go/api/pipelines/" project-name "/history")) (defn http-get-basic-auth ;; instead of `fetch-pipeline', because now this operation doesn't care about a specific type of URL [well-formed-url] ;; it can simply assume someone gives it a well-formed URL. (client/get well-formed-url {:basic-auth "username:password"})) ;; we'll see about this hard-coding later (defn pipeline-build-count ;; now this only cares about looking up build count, so no "GET" semantics ;; assumes it gets a well-formed response [{:keys [body] :as response}] ;; destructuring for convenience and function API documentation (-> body (parse-string true) :pipelines first :counter)) (defn fetch-pipeline-counts! ;; ties all the pieces together [project-names] (reduce (fn [builds project-name] ;; uses reduce, not map, because: https://stuartsierra.com/2015/08/25/clojure-donts-lazy-effects (conj builds (-> project-name pipeline-api-endpoint http-get-basic-auth pipeline-build-count))) [] project-names)) Now... It turns out that fetch-pipeline-counts! is a giant effectful process, tied directly to http-get-basic-auth. We could try to lift out the effectful part, and try to make it a pure function. (defn http-get-basic-auth [well-formed-url username password] (client/get well-formed-url {:basic-auth (str username ":" password)})) (defn http-basic-auth-getter "Given basic auth credentials, return a function that takes an HTTP endpoint, and GETs data from there." [username password] (fn [well-formed-url] (http-get-basic-auth well-formed-url username password))) (defn fetch-pipeline-counts-alt [pipeline-fetcher project-names] ;; Easier to unit test. We can pass a mock fetcher that doesn't call over the network. ;; In fact, we can now use any kind of "fetcher", even read from a DB or file where we may have dumped raw GET results. (reduce (fn [builds project-name] (conj builds (-> project-name pipeline-api-endpoint pipeline-fetcher pipeline-build-count))) [] project-names)) (comment (fetch-pipeline-counts-alt (http-basic-auth-getter "username" "password") ["projectA" "projectB" "projectC" "projectD"]) ) A closer look might suggest that we're now describing processes that could be much more general than fetching pipeline counts from an HTTP endpoint... Enjoy Clojuring! :) On Monday, December 14, 2020 at 10:51:52 PM UTC+5:30 jamesl...@gmail.com wrote: > Very cool everyone. This is exactly the kind of feedback I was hoping for. > I'm going through Clojure for the Brave and I hadn't made it to the macros > chapter yet. That single threading macro is pretty sweet! > > Thanks everyone! > > On Monday, December 14, 2020 at 11:00:02 AM UTC-6 brando...@gmail.com > wrote: > >> Hey James, >> >> Another small suggestion is you can just pass println to map, since it >> takes 1 argument in your case. >> >> (map println (sort builds)) >> >> But here, since you just want to perform side effects, maybe run! would >> be a better function to use. >> >> (run! println (sort builds)) >> >> This would cause it to return just one nil. Clojure is a functional >> language, and every function returns a value. You'll see the return in >> your REPL, but if the program is run in another way, such as packaged as a >> jar, you would just see the print output. >> >> - Brandon >> >> On Mon, Dec 14, 2020 at 8:42 AM Justin Smith <noise...@gmail.com> wrote: >> >>> a small suggestion: you don't need to nest let inside let, a clause >>> can use previous clauses: >>> >>> (defn get-latest-build >>> [pipeline] >>> (let [response (fetch-pipeline pipeline) >>> json (parse-string (:body response) true) >>> [pipeline] (:pipelines json)] >>> (:counter pipeline)))) >>> >>> also consider using get-in: >>> >>> (defn get-latest-build >>> [pipeline] >>> (let [response (fetch-pipeline pipeline) >>> json (parse-string (:body response) true)] >>> (get-in json [:pipelines 0 :counter]))) >>> >>> finally, this can now be simplified into a single threading macro: >>> >>> (defn get-latest-build >>> [pipeline] >>> (-> (fetch-pipeline pipeline) >>> (:body) >>> (parse-string true) >>> (get-in [:pipelines 0 :counter]))) >>> >>> On Mon, Dec 14, 2020 at 7:18 AM James Lorenzen <jamesl...@gmail.com> >>> wrote: >>> > >>> > Hello all, >>> > This is my first Clojure program and I was hoping to get some advice >>> on it since I don't know any experienced Clojure devs. I'm using it locally >>> to print the latest build numbers for a list of projects. >>> > >>> > ``` >>> > (ns jlorenzen.core >>> > (:gen-class) >>> > (:require [clj-http.client :as client]) >>> > (:require [cheshire.core :refer :all])) >>> > >>> > (defn fetch-pipeline >>> > [pipeline] >>> > (client/get (str "https://example.com/go/api/pipelines/" pipeline >>> "/history") >>> > {:basic-auth "username:password"})) >>> > >>> > (defn get-latest-build >>> > [pipeline] >>> > (let [response (fetch-pipeline pipeline) >>> > json (parse-string (:body response) true)] >>> > (let [[pipeline] (:pipelines json)] >>> > (:counter pipeline)))) >>> > >>> > (def core-projects #{"projectA" >>> > "projectB" >>> > "projectC" >>> > "projectD"}) >>> > >>> > (defn print-builds >>> > ([] >>> > (print-builds core-projects)) >>> > ([projects] >>> > (let [builds (pmap #(str % " " (get-latest-build %)) projects)] >>> > (map #(println %) (sort builds))))) >>> > ``` >>> > >>> > This will output the following: >>> > ``` >>> > projectA 156 >>> > projectB 205 >>> > projectC 29 >>> > projectD 123 >>> > (nil nil nil nil) >>> > ``` >>> > >>> > A few questions: >>> > >>> > How can this program be improved? >>> > How idiomatic is it? >>> > How can I prevent it from returning the nils at the end? I know this >>> is returning nil for each map'd item; I just don't know the best way to >>> prevent that. >>> > >>> > Thanks, >>> > James Lorenzen >>> > >>> > -- >>> > You received this message because you are subscribed to the Google >>> > Groups "Clojure" group. >>> > To post to this group, send email to clo...@googlegroups.com >>> > Note that posts from new members are moderated - please be patient >>> with your first post. >>> > To unsubscribe from this group, send email to >>> > clojure+u...@googlegroups.com >>> > For more options, visit this group at >>> > http://groups.google.com/group/clojure?hl=en >>> > --- >>> > You received this message because you are subscribed to the Google >>> Groups "Clojure" group. >>> > To unsubscribe from this group and stop receiving emails from it, send >>> an email to clojure+u...@googlegroups.com. >>> > To view this discussion on the web visit >>> https://groups.google.com/d/msgid/clojure/ccb868e0-7e0c-46df-80fc-712f718314e3n%40googlegroups.com >>> . >>> >>> -- >>> You received this message because you are subscribed to the Google >>> Groups "Clojure" group. >>> To post to this group, send email to clo...@googlegroups.com >>> Note that posts from new members are moderated - please be patient with >>> your first post. >>> To unsubscribe from this group, send email to >>> clojure+u...@googlegroups.com >>> For more options, visit this group at >>> http://groups.google.com/group/clojure?hl=en >>> --- >>> You received this message because you are subscribed to the Google >>> Groups "Clojure" group. >>> To unsubscribe from this group and stop receiving emails from it, send >>> an email to clojure+u...@googlegroups.com. >>> >> To view this discussion on the web visit >>> https://groups.google.com/d/msgid/clojure/CAGokn9L65oxePmfJqEDNvyhS9XL-JFjDbQAfk5zdiRctXS_-bQ%40mail.gmail.com >>> . >>> >> -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/clojure/ec3eb75e-4852-4525-9bb6-2a1e6baa4375n%40googlegroups.com.