On Wednesday, December 16, 2020 at 3:12:22 AM UTC+5:30 jamesl...@gmail.com 
wrote:

> 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?
>

Yes it's "associative destructuring". The main purpose of destructuring is 
to conveniently bind a name to a value found somewhere inside a data 
structure. The additional benefit is that it also serves to document the 
function API, which IDE tooling can surface.

The term "destructuring" sounded heavy when I first tried to grok it. It 
got easier when I started thinking of it like visually matching shapes (of 
name bindings) to shapes (of data). Sort of like a fancy mask or a cookie 
cutter pattern. To illustrate:

;; If I see this:

(let [[a b c & others]
      [1 2 3 4 5 6]]
  ;; do something
  )

;; I visualise the effect of destructuring as:

1  2  3    4 5 6
|  |  |   (     )
v  v  v      v
[a  b  c & others]
 

> On Tuesday, December 15, 2020 at 2:33:08 PM UTC-6 James Lorenzen wrote:
>
>> 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.
>>
>
There are at least two ways to do this. In both cases, I'd favour returning 
a hash-map, instead of tuples. 

One way is to modify `pipeline-build-count` like this:

(defn pipeline-build-count
  [{:keys [body] :as response}]
  {:project-name (:the-key-for-project-name body)
   :latest-build-count (-> body
                           (parse-string true)
                           :pipelines
                           first
                           :counter)})

The other way is in the fetch-pipeline-counts function. Something like this:

(defn fetch-pipeline-counts-alt
  [pipeline-fetcher project-names]
  (reduce (fn [builds project-name]
            (conj builds
                  {:project-name project-name
                   :latest-build-count (-> project-name
                                           pipeline-api-endpoint
                                           pipeline-fetcher
                                           pipeline-build-count)}))
          []
          project-names))

If the name is the only relevant detail that matters, then either way is 
fine. However, I'd modifying the pipeline-build-count function, because it 
has access to all the information about a pipeline, and we may want to 
process and/or pass through more of it later.

 

> Thanks for all the help! I'm learning a ton.
>>
>
Cheers :)

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/ce0f8427-3c23-4d1a-b413-1a3f3a2a990an%40googlegroups.com.

Reply via email to