Seconding aditya's advice here. I can truly recommend watching this talk
about solving problems the Clojure way:

https://youtu.be/vK1DazRK_a0

Interestingly, he applies it on a JavaScript code example. Which makes it
all that much more powerful.

On Tue, 15 Dec 2020 at 22:42, James Lorenzen <jamesloren...@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?
>
> 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
> <https://groups.google.com/d/msgid/clojure/33bbba06-2eae-4e2d-8bf2-7322fe7d4db5n%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>

-- 
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/CAMX9Yz9uTYa0uJ6gCCQLApdSjvn0PO9auy%3DybgwJrFg2t71ReA%40mail.gmail.com.

Reply via email to