So I think I can answer my first question. That particular line in 
`pipeline-build-count` is doing associative destructing 
<https://clojure.org/guides/destructuring#_associative_destructuring>. And 
you didn't have to do the `as response` but like you said in your comment, 
you are just doing that to sort of document the method. That sound about 
right?

On Tuesday, December 15, 2020 at 2:33:08 PM UTC-6 James Lorenzen wrote:

> Thanks for the suggestions aditya. I definitely like where you are headed. 
> I have a few questions. This syntax in `pipeline-build-count` method looks 
> confusing to me:
> > [{:keys [body] :as response}] ;; destructuring for convenience and 
> function API documentation
>
> Would you mind breaking that down a little more?
>
> Also, your program works great though it only returns a list of the latest 
> build numbers without the project name. I'm trying to think of how I could 
> include that but I'm not seeing it. The fetch pipeline response does 
> include the project name so I could just return it and the counter; so I 
> could return a tuple? or just two values (I think clojure can support 
> that). I'd rather just thread the project-name through though.
>
> Thanks for all the help! I'm learning a ton.
> On Monday, December 14, 2020 at 11:35:15 PM UTC-6 aditya....@gmail.com 
> wrote:
>
>> 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/33bbba06-2eae-4e2d-8bf2-7322fe7d4db5n%40googlegroups.com.

Reply via email to