Fork me on GitHub
#beginners
<
2020-10-29
>
Jim Newton07:10:24

what is a best way to generate a lazy sequence of ordered pairs from a given sequence? The output sequence can be in any order, I don't care. For example given (1 2 3 4), generate the sequence [[1 2] [1 3] [1 4] [2 3] [2 4] [3 4]] which does not include [1 1] (repeated elemente) unless 1 occurs twice in the input sequence, and does not include [2 1] because 2 does not precede 1 in the input sequence.

Jim Newton07:10:01

Here's what I'm using, but I don't like calling rest on something that might be an array.

(defn lazy-pairs [seq]
  (cond (empty? seq)
        ()

        :else
        (concat (for [t (rest seq)]
                  [(first seq) t])
                (lazy-pairs (rest seq)))))

Ben Sless09:10:18

Another option is using next, but it's not much better. Here's what I came up with

(defn lazy-pairs
  ([coll]
   (lazy-seq
    (when-let [s (seq coll)]
      (let [[h & t] coll]
        (concat
         (map #(list h %) t)
         (lazy-pairs t)))))))
Why are you worried about it being an array? Why does it have to be lazy?

Jim Newton09:10:38

the reason I was trying to make it lazy was that the list might be long sometimes. and if i'm using something like (some pred (lazy-pairs ...)) i'd like that to stop generating when it finds a match

Ben Sless10:10:41

Makes sense. I wonder if it can be expressed as a transducer, then you won't have an issue with laziness / next

yuhan10:10:06

@U010VP3UY9X note that your seq argument is shadowing the clojure.core function, it's more idiomatic to name it coll

yuhan10:10:56

(defn lazy-pairs [coll]
  (if-let [[x & xs] (seq coll)]
    (concat (for [x' xs]
              [x x'])
      (lazy-pairs xs))
    ()))

Jim Newton11:10:58

what does if-let do when you bind multiple variables?

Ben Sless12:10:07

if-let supports only one binding form, which can contain destructuring, i.e. (if-let [[x & xs] ,,,) is valid, but (if-let [x ,,, y ,,,] ,,,) is invalid

Ben Sless12:10:31

You can find online custom versions of if-let which support multiple bindings, usually called if-let*

jkida09:10:38

Is there a (better) alternative using some higher order functions to avoid using loop/recur for the following:

(defn demo-loop-recur [n]
  "update n maps of an ordered vec where (:changeable? m),
    when changeable? then update (assoc m :did-change? true)
    else
    (assoc m :did-change? false)
  "
  (let [demo-vec [{:id 1 :changeable? true}
                  {:id 2 :changeable? false}
                  {:id 3 :changeable? true}
                  {:id 4 :changeable? false}
                  {:id 5 :changeable? true}
                  {:id 6 :changeable? true}
                  {:id 7 :changeable? true}]]
    (loop [remain-coll  demo-vec
           change-count 0
           result       []]
      (if (empty? remain-coll)
        result
        (let [[m & remaining] remain-coll
              should-change (and (< change-count n)
                                 (:changeable? m))
              new-cnt       (if should-change
                              (inc change-count)
                              change-count)]
          (recur remaining
                 new-cnt
                 (into result [(assoc m :did-change? should-change)])))))))

jkida09:10:12

Is loop/recur what i should be reaching for here?

mbjarland11:10:37

Generic question. Assume I have a reasonably complex xml file and I want to edit it in clojure (i.e. parse into data, make some edits, emit back to xml). The editing consists of finding a number of nodes using some predicates and changing their content in some way, perhaps adding a new child node, changing an attribute etc. I am more or less familiar with zippers and say the xml-> function for selecting data from a document, but have failed to find a clean way of doing a multi node edit. It seems to me that xml-> does not immediately play nice with zip/edit as you would need to essentially reduce over the to-be-edited nodes/locs located by xml-> and I haven't found a clean way to do this. Then again, I might just be missing something obvious. I can of course treat the parsed xml structure as data and start doing update-in etc, but that gets very chatty and I'm hoping there is a cleaner/more concise/more idiomatic way. Any help much appreciated.

Stuart11:10:26

@jdkida

(defn reducer [acc curr]
  (let [{:keys [changes-remaining result]} acc]
    (if (and (> changes-remaining 0)
             (:changeable? curr))
      (assoc (update acc :result conj (assoc curr :did-change true)) :changes-remaining (dec (:changes-remaining acc)))
      (assoc (update acc :result conj (assoc curr :did-change false)) :changes-remaining (:changes-remaining acc)))))

(let [demo-vec [{:id 1 :changeable? true}
                  {:id 2 :changeable? false}
                  {:id 3 :changeable? true}
                  {:id 4 :changeable? false}
                  {:id 5 :changeable? true}
                  {:id 6 :changeable? true}
                  {:id 7 :changeable? true}]]
    (:result (reduce reducer {:changes-remaining 5 :result []} demo-vec)))
;=> [{:id 1, :changeable? true, :did-change true}
     {:id 2, :changeable? false, :did-change false}
     {:id 3, :changeable? true, :did-change true}
     {:id 4, :changeable? false, :did-change false}
     {:id 5, :changeable? true, :did-change true}
     {:id 6, :changeable? true, :did-change true}
     {:id 7, :changeable? true, :did-change true}]


(:result (reduce reducer {:changes-remaining 1 :result []} demo-vec))
;=> [{:id 1, :changeable? true, :did-change true}
     {:id 2, :changeable? false, :did-change false}
     {:id 3, :changeable? true, :did-change false}
     {:id 4, :changeable? false, :did-change false}
     {:id 5, :changeable? true, :did-change false}
     {:id 6, :changeable? true, :did-change false}
     {:id 7, :changeable? true, :did-change false}]
Id be interested if someone with more experience (I'm pretty much a beginner so my idea might be completely wrong nonsense) can show a way to do reduce where you need to keep track of multiple things, in this instance change-count and the result ?

vlaaad12:10:23

Maybe something along the lines of

(defn change-first-n [n xs]
  (let [remaining (volatile! n)]
    (mapv (fn [x]
            (assoc x :did-change (or (not (pos? (vswap! remaining dec)))
                                     (:changeable? x))))
          xs)))

vlaaad12:10:02

local mutability that does not escape the scope if fine, you have to be careful of course…

Stuart12:10:29

thanks, I hadn't come across (volatile!) before 🙂

jkida14:10:29

Interesting, thanks guys.

jkida19:10:57

Here is another version i came up with.. i dont think its better then the reduce version though.

(defn demo-mapped-index [n]
  "Alternative Option
  First track vec by idx and set everything to did-change? false
  Then filter by changeable and take n.
  Using the idx tracked from step1 update specific items.
  "
  (let [demo-vec [{:id 1 :changeable? true}
                  {:id 2 :changeable? false}
                  {:id 3 :changeable? true}
                  {:id 4 :changeable? false}
                  {:id 5 :changeable? true}
                  {:id 6 :changeable? true}
                  {:id 7 :changeable? true}]]
    (let [idx-demo-vec (map-indexed (fn [idx itm] (assoc itm :idx idx :did-change? false)) demo-vec)
          filter-n     (take n (filter #(:changeable? %) idx-demo-vec))]
      (apply assoc (vec idx-demo-vec) (mapcat (fn [x] [(:idx x) (assoc x :did-change? true)]) filter-n)))))

Eamonn Sullivan12:10:19

Hi everyone. I'm in the middle of refactoring an older bit of code (that I wrote when first learning clojure) and I have a view instances of patterns like this:

(defn get-page-of-stuff
  [access-token org topics page-size cursor]
  (core/make-graphql-post
   access-token
   (core/get-graphql "search-query")
   {:first page-size :query (get-query org topics) :after cursor}))

(defn get-all-pages
  [access-token org topics page-size]
  (let [page (get-page-of-stuff access-token org topics page-size nil)]
    (loop [page page
           result []]
      (let [pageInfo (-> page :data :search :pageInfo)
            has-next (pageInfo :hasNextPage)
            cursor (pageInfo :endCursor)
            result (concat result (get-nodes page))]
        (if-not has-next
          (into [] result)
          (recur (get-page-of-stuff access-token org topics page-size cursor)
                 (get-nodes page)))))))
I seem to remember that this loop/recur thing is quite low-level and there might be a better, more functional approach. Basically, I get a page of results and a pageInfo object that has a hasNextPage property. If true, I get the next page, if not, I'm done. How would someone with more of a clue than me do this?

ghadi13:10:15

@eamonn.sullivan see the function attached to this ticket https://clojure.atlassian.net/browse/CLJ-2555 for a useful way to do this

ghadi13:10:14

the docstring is a bit complicated but the step! argument is a function that gets the initial or next page

ghadi13:10:00

Ask me anything about it and I’ll try to help async

Eamonn Sullivan08:10:54

Many, many thanks for this. I learned quite a bit. I drastically dumbed it down to my (basic) level, because I have a very specific use case (paging through Github API searches), but it still meant I could consolidate several specific functions into a general, reusable one. I can replace my iterate-pages with the real core/iteration when 1.11 comes out.

(defn iterate-pages
  "Iterate through the pages of a Github GraphQL search.

  pager -- cursor -> page function to get a page of results.
  results? -- page -> boolean function that returns true if the page contains values.
  vf -- page -> values function that extracts the values from a page.
  kf -- page -> cursor function that extracts the cursor for the next page, or nil if there isn't one."
  [pager results? vf kf]
  (reify
    clojure.lang.Seqable
    (seq [_]
      ((fn next [ret]
         (when (results? ret)
           (concat (vf ret)
                   (when-some [k (kf ret)]
                     (lazy-seq (next (pager k)))))))
       (pager nil)))))

(defn get-all-pages
  "Convenience function for getting all of the results from a paged search.

  getter -- function that returns a single page, given a cursor string.
  results? -- function that returns a boolean indicate whether the page contains values.
  valuesfn -- function to extract the values from a page."
  [getter results? valuesfn]
  (let [get-next (fn [ret] (if (-> ret :data :search :pageInfo :hasNextPage)
                             (-> ret :data :search :pageInfo :endCursor)
                             nil))]
    (into [] (map identity (iterate-pages getter results? valuesfn get-next)))))
An example use:
(defn get-page-of-repos
  [access-token org topics page-size cursor]
  (core/make-graphql-post
   access-token
   (core/get-graphql "repo-search-query")
   {:first page-size :query (get-query org topics) :after cursor}))

(defn get-repos
  "Get information about repos in a given organisation, with the specified topics"
  ([access-token org topics] (get-repos access-token org topics *default-page-size*))
  ([access-token org topics page-size]
   (let [get-page (partial get-page-of-repos access-token org topics page-size)
         results? (fn [page] (some? (get-nodes page)))]
     (core/get-all-pages get-page results? get-nodes))))

ghadi18:10:22

Don't do recursive concat

Eamonn Sullivan19:10:46

Ah, yes, sorry. I didn't copy that right. The original code used concat.

Eamonn Sullivan19:10:34

In fact, I'm beginning to think the original code might have been fine. I can use into [] there and I don't think I can do that with yours.

Eamonn Sullivan12:11:14

I did eventually figure it out and use cons instead, which gets me a lazy sequence of pages (seq of seqs) instead of a individual search results. What I meant about the original code is that this is for a simple CLI that produces a JSON file on disk. I'm not processing them one at a time or something, where the lazy sequence would make a lot more sense. But perhaps I should be... :thinking_face: Always learning! Thanks for the help.

Malik Kennedy16:10:48

What am I missing to get a secure (as-in CSRF) POST input form (using reitit / luminus?) #+FILE: guestbook/routes/home.clj

(defn chat-page [request]
  (layout/render request "chat.html" 
    {:messages (db/get-all-messages)}))

(defn save-message [message]
  (db/create-message! message)
  (response/found "/chat"))

(defn home-routes []
  [""
   {:middleware [middleware/wrap-csrf
                 middleware/wrap-formats]}
   ["/" {:get home-page}]
   ["/about" {:get about-page}]
   ["/chat" 
    {:get chat-page}]
   ["/chat/:content"
    {:post save-message}]
])
#+FILE: guestbook/resources/html/chat.html
<form action="/chat" method="POST">
      {% csrf-field %}
      <textarea id="content" rows="3"></textarea>
      <button>submit</button>
    </form>
 <ul>
   {% for message in messages %}
   <li> {{ message }} </li>
   {% endfor %}
 </ul>
#+FILE: guestbook/resources/sql/queries.sql
-- :name get-all-messages :? :*
-- :doc retrieves all messages records
SELECT * FROM chat

-- :name create-message! :! :n
-- :doc creates a new message record
INSERT INTO chat
(content)
VALUES (:content)

Roger Amorin Vieira18:10:13

Someone can give me an explain why the both did not give the same result?

(reduce into '(("a" "b" "c") ("d" "e" "f")))
; ("c" "b" "a" "a" "b" "c")

(reduce into [["a" "b" "c"] ["a" "b" "c"]])
; ["a" "b" "c" "a" "b" "c"]

Ben Sless18:10:24

Vectors accumulate at the end, lists accumulate at the beginning. You can see this difference with conj, too

practicalli-johnny20:10:16

into is basically reduce conj

phronmophobic18:10:13

into uses conj. For lists > https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/conj puts the item at the front of the list. For vectors > https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/conj puts the item at the end of the vector Since no initial value is provided to reduce: > If [an initial] val is not supplied, returns the result of applying f to the first 2 items in coll see https://clojure.org/reference/data_structures#Lists

hiredman18:10:29

always supply an initial value to reduce is a good habit to get in to

18
Roger Amorin Vieira19:10:39

Thanks, for all help

Rafał Wyszomirski21:10:44

Good evening. Can you recommend some good books about Clojure? I like Pragmatic Bookshelf books and I have always considered them of high quality. What do you think?

practicalli-johnny11:10:37

If you are okay with online books, then there are several I am working on at https://practicalli.github.io/

👍 3
Alex Miller (Clojure team)21:10:44

the Clojure books on Pragmatic are great; you should buy them all

Alex Miller (Clojure team)21:10:17

(shh, no one tell him I co-authored two of them)

😄 12
Rafał Wyszomirski21:10:56

I have the first edition of "Programming Clojure" but I think I am more interested in web development. The third edition looks great though, I will wait for the sale you have mentioned 🙂

Alex Miller (Clojure team)21:10:58

definitely a lot of updates between 1st and 3rd editions. I myself learned Clojure from the 1st edition!

Alex Miller (Clojure team)21:10:43

if you are willing to wait a bit, they will be having their yearly 40% off sale on e-books over Thanksgiving in a few weeks

Rafał Wyszomirski21:10:52

Great news, thank you. I must say they're pretty hard to get in print unfortunately

Alex Miller (Clojure team)21:10:35

they make great gifts, I recommend buying 10 copies of each

🎄 9
🎁 9
😆 15
seancorfield21:10:34

Joking from the author aside, I have the following Pragmatic books on Clojure: Clojure Applied, Getting Clojure, Programming Clojure (both the 2nd Ed and the 3rd Ed), and Web Development with Clojure (3rd Ed) -- and they're all really good. I also have Programming Concurrency on the JVM and Seven Languages in Seven Weeks, which both feature some Clojure too 🙂

seancorfield21:10:23

@rawyszo I hear good things about Living Clojure as a beginner's book (O'Reilly). I learned Clojure mostly from Joy of Clojure (Manning) and Clojure Programming (O'Reilly) but that was a long time ago.

Rafał Wyszomirski22:10:27

I like Manning quite a lot so I think I will take a look at Joy of Clojure. Thanks for all the help 🙂

seancorfield22:10:51

JoC is very much the "why" of Clojure. It's a good second book on Clojure.

👍 3
seancorfield21:10:45

O'Reilly's "Clojure Cookbook" is a good, practical book too (although it's from 2014 so some of its content may be a bit dated now, and some of the libraries it discusses have been superseded by better choices).

Alex Miller (Clojure team)22:10:58

I think most of it is now pretty dated unfortunately. I've been contemplating how to make that idea live again

seancorfield22:10:49

I would be happy to contribute updated material for the JDBC stuff (I helped out with the original JDBC content).

Rafał Wyszomirski22:10:11

Fun fact - I've a actually switched to Emacs and started learning Clojure because of Clojure for the Brave and True (the book is hilarious btw, love it)

Rafał Wyszomirski22:10:23

Anyway, thank you for all the suggestions. 🙏 I also wanted to ask if there are some more general resources about dealing with the whole env/ecosystem? More specifically I'm talking about configuration, architecture, tools etc. Or maybe some useful articles about leiningen, shadow-cljs and figwheel would be great. There's always googling things but it would be nice to have some good examples :)

seancorfield23:10:44

Hard to say in general @rawyszo. The http://clojure.org site has information about the Clojure CLI and deps.edn which you'll see in some newer books and tutorials: https://clojure.org/guides/deps_and_cli

seancorfield23:10:06

For architecture, Clojure Applied covers that to some degree as I recall, but you'll find folks aren't very proscriptive in Clojure, in general. There's some stuff out there about Domain-Driven Design in Clojure. The most general advice you'll get is to separate the pure functions from the side-effecting functions -- "functional core, imperative shell" is another approach you'll hear people talk about.

seancorfield23:10:38

For Leiningen, Shadow, Figwheel, etc -- I'd say their respective websites are the definitive documentation. There are lots of channels here when you need to dig deeper on stuff: #tools-deps (for deps/CLI) #leiningen #figwheel-main #figwheel #lein-figwheel #shadow-cljs #architecture ... that should keep you busy for a while 🙂

seancorfield23:10:20

(there are also quite a few "local" channels which can be good places to find additional resources -- #clojure-poland assuming you're from there?)

Rafał Wyszomirski07:10:53

Yup :) thank you for all the answers 👍 As you said, that should keep me busy. Have a good day 🙂