1

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?

2
  • library/create-* functions act entirely through side effects? They don't return anything? Commented Nov 20, 2017 at 17:26
  • If that is the case, nested doseqs are probably the neatest here. Note that for the first 2 doseqs at least, you can compress them down to (doseq [library-seq ..., shelves-seq ...] ...). You don't need a new doseq for every sequence. They can be grouped like with fors. Commented Nov 20, 2017 at 17:31

1 Answer 1

5

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})))
Sign up to request clarification or add additional context in comments.

2 Comments

Basically what I hash in my head. The _ (library/create-shelf part is unfortunate though. I'd almost give the third seq its own doseq just so let isn't being used to carry out side effects.
Yeah, I agree that that's a bit gross. A :do would be nicer if it existed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.