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.