Search code examples
loopsclojurenested

Clojure nested doseq loop


I'm new to Clojure and I have a question regarding nested doseq loops.

I would like to iterate through a sequence and get a subsequence, and then get some keys to apply a function over all the sequence elements.

The given sequence has an structure more or less like this, but with hundreds of books, shelves and many libraries:

([:state/libraries {6 #:library {:name "MUNICIPAL LIBRARY OF X" :id 6 
:shelves {3 #:shelf {:name "GREEN SHELF" :id 3 :books  
{45 #:book {:id 45 :name "NECRONOMICON" :pages {...}, 
{89 #:book {:id 89 :name "HOLY BIBLE" :pages {...}}}}}}}}])

Here is my code:

(defn my-function []   (let [conn  (d/connect (-> my-system :config :datomic-uri))]
(doseq [library-seq (read-string (slurp "given-sequence.edn"))]
  (doseq [shelves-seq (val library-seq)]
    (library/create-shelf conn {:id (:shelf/id (val shelves-seq)) 
                                :name (:shelf/name (val shelves-seq))})
    (doseq [books-seq (:shelf/books (val shelves-seq))]
      (library/create-book conn (:shelf/id (val shelves-seq)) {:id (:book/id (val books-seq))
                                                               :name (:book/name (val books-seq))})
                                                               )))))

The thing is that I want to get rid of that nested doseq mess but I don't know what would be the best approach, since in each iteration keys change. Using recur? reduce? Maybe I am thinking about this completely the wrong way?


Solution

  • Like Carcigenicate says in the comments, presuming that the library/... functions are only side effecting, you can just write this in a single doseq.

    (defn my-function []
      (let [conn  (d/connect (-> my-system :config :datomic-uri))]
        (doseq [library-seq (read-string (slurp "given-sequence.edn"))
                shelves-seq (val library-seq)
                :let [_ (library/create-shelf conn
                                              {:id (:shelf/id (val shelves-seq))
                                               :name (:shelf/name (val shelves-seq))})]
                books-seq (:shelf/books (val shelves-seq))]
          (library/create-book conn
                               (:shelf/id (val shelves-seq))
                               {:id (:book/id (val books-seq))
                                :name (:book/name (val books-seq))}))))
    

    I would separate "connecting to the db" from "slurping a file" from "writing to the db" though. Together with some destructuring I'd end up with something more like:

    (defn write-to-the-db [conn given-sequence]
      (doseq [library-seq given-sequence
              shelves-seq (val library-seq)
              :let [{shelf-id   :shelf/id,
                     shelf-name :shelf/name
                     books      :shelf/books} (val shelves-seq)
                    _ (library/create-shelf conn {:id shelf-id, :name shelf-name})]
              {book-id :book/id, book-name :book/name} books]
        (library/create-book conn shelf-id {:id book-id, :name book-name})))