2
\$\begingroup\$

I am trying to find all "link" elements that link to RSS feeds in a data structure created by clj-tagsoup. I wrote the following code, which seems to work fine. But: I come from a Java background and I am new to functional programming and clojure, so I am not sure if this is the right way to do it. Could you please give me some hints about what I could improve in the following code:

(defn- matches-attributes [current-attributes required-attributes]
    (reduce #(and %1 (= (second %2) ((first %2) current-attributes)))
            true 
            required-attributes))

(defn- find-tag [tag-name current-tag attributes]
    (if (= tag-name (tagsoup/tag current-tag))
        current-tag
        (filter #(and (= tag-name (tagsoup/tag %1)) 
                (matches-attributes (tagsoup/attributes %1) attributes))
                (tagsoup/children current-tag))))

(defn- find-rss-feed [page-result]
    (let [document (tagsoup/parse-string (:body page-result))
          head (first (find-tag :head document nil))
          rss-links (if (nil? head) 
                        nil 
                        (find-tag :link head {:type "application/rss+xml"}))]
    ... do something with rss-links...))

Edit To clarify a bit, what I am particularily interested in is if traversing the HTML tree the way I do it is the "right" was in a functional language. What I'm also interested in is if "filter" and "reduce" are a good choice as I use them, or if there is a better way. And, of course, I'd also appreciate general hints about what I could do better in this little program...

\$\endgroup\$
5
\$\begingroup\$

It's not bad, but we can make it a bit more idiomatic. Let's look at this function first:

(defn- matches-attributes [current-attributes required-attributes]
    (reduce #(and %1 (= (second %2) ((first %2) current-attributes)))
            true 
            required-attributes))

The use of reduce is a bit odd, instead let's use every? Secondly, if current-attributes is a hash map or a set, then we can actually use it as a function. Since every? takes a predicate, we can rewrite the entire function as:

(defn- matches-attributes [current-attributes required-attributes]
  (every? (fn [tv tg] 
              (= (current-attributes tg) tv))
          required-attributes))

On the last function, when-let will help you clean it up a bit more:

(defn- find-rss-feed [page-result]
    (let [document (tagsoup/parse-string (:body page-result))
          rss-links (when-let [head (first (find-tag :head document nil))]
                        (find-tag :link head {:type "application/rss+xml"}))]
    ... do something with rss-links...))

Hopefully this helps a bit. Your code doesn't really support this, but the cool thing to remember is that Clojure collections are functions that return nil if the item doesn't exist. So for example:

=> (remove #{1 2 3} [1 2 3 4 5 6 7])
(4 5 6 7)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.