RSS feed viewer in ClojureDisplaying RSS feeds from Google Feed API as HTML listRSS feed class in C#RSS Feed Calendar PluginHow can I speed up my RSS feed Android App?RSS feed parserDrop-down list with 14 options of RSS feed datesUnit-testing an RSS feed parserBookmarklet to go to YouTube channel RSS feedRSS Parser for Blog FeedParse atom RSS feed with xml.dom.minidom

What killed these X2 caps?

Withdrawals from HSA

How do conventional missiles fly?

Can I use a neutral wire from another outlet to repair a broken neutral?

Is it legal for company to use my work email to pretend I still work there?

What's the difference between 'rename' and 'mv'?

Brothers & sisters

How to model explosives?

What mechanic is there to disable a threat instead of killing it?

Alternative to sending password over mail?

Why does Arabsat 6A need a Falcon Heavy to launch

Doing something right before you need it - expression for this?

Should I tell management that I intend to leave due to bad software development practices?

Can a rocket refuel on Mars from water?

How do I find out when a node was added to an availability group?

Arrow those variables!

How much of data wrangling is a data scientist's job?

In Romance of the Three Kingdoms why do people still use bamboo sticks when papers are already invented?

What is the word for reserving something for yourself before others do?

Is it unprofessional to ask if a job posting on GlassDoor is real?

prove that the matrix A is diagonalizable

Reserved de-dupe rules

How to show the equivalence between the regularized regression and their constraint formulas using KKT

How can I prevent hyper evolved versions of regular creatures from wiping out their cousins?



RSS feed viewer in Clojure


Displaying RSS feeds from Google Feed API as HTML listRSS feed class in C#RSS Feed Calendar PluginHow can I speed up my RSS feed Android App?RSS feed parserDrop-down list with 14 options of RSS feed datesUnit-testing an RSS feed parserBookmarklet to go to YouTube channel RSS feedRSS Parser for Blog FeedParse atom RSS feed with xml.dom.minidom






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;








2












$begingroup$


This semester a classmate and I visited a course on functional programming in our university. For grading, we've to submit a small project which should be written in Clojure and make use of functional programming.
However, the topic of the course itself was mostly about Clojure and its syntax and not so much about functional programming itself. That's why we're not quite sure if our code complies with functional programming paradigms.



It would be highly appreciated if someone takes a look at our code to point out flaws and possible violations of functional programming.
If you see anything that could be written simpler or seems confusing to you, please point it out, since we're new to Clojure and not sure if we've done everything correctly or in the most efficient way (probably not).



Following below is the main part of our code. The entire project can be found in our Gitlab repository.
Since it's a web app we've used hiccup, ring and compojure.
The main functionalities are a landing page at /, which lets you either put in a RSS Feed url or select one RSS Feed from categories provided by us.
Once you submit you're routed to /feed. Here we render the separate items of the current feed.
You've the possibility to filter the feed by typing something, like 'Trump' and as a result we only show the feed items that include the word 'Trump' either in the title or the description. Additionally those "matches" are afterwards highlighted like so:



enter image description here



Furthermore, there's the possibility to filter by author. All authors of the current feed are available as an option in a select box.



Code
feed.clj



 (ns rss-feeds-webapp.feed
(:require
[compojure.core :refer :all]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[clj-http.client :as client]
[clojure.java.io :as io]
[clojure.xml :as xml]
[clojure.string :as string])
(:import [java.io ByteArrayInputStream])
(:use
[rss-feeds-webapp.helper :refer :all]
))

;; gets the feed from the url, then parses it to an byte array and then to an xml sequenze
(defn get-feed [link]
(xml-seq (xml/parse (io/input-stream (ByteArrayInputStream. (.getBytes (:body (client/get link)) "UTF-8"))))) )

;; returns the content of the xml tag
;; @param [item] xml-seq the post item which contains the title, description and link7
;; @param [tag] key the tag, that contains the information you want, e.g. title
(defn get-content [item tag]
(for [x item
:when (= tag (:tag x))]
(first (:content x))))

;; creates an hash-map for the feed item, containing the title, description, link, creator and pubDate (if it exists)
;; param [item] xml-seq the post item,
(defn get-item [item]
:title (get-content item :title )
:description (get-content item :description)
:link (get-content item :link)
:pubDate (get-content item :pubDate)
:image (get-content item :image)
:creator (get-content item :dc:creator)

)

;; creates an hash-map for the feed item, containing the creator
;; param [item] xml-seq the post item,
(defn get-author [item]
:creator (get-content item :dc:creator)
)

;; finds all feed items and calls the get-item function for each one
;; @param [item] xml-seq the post item which contains the title, description, creator and link
(defn get-items [ return-items link]
(for [x (get-feed link)
:when (= :item (:tag x))]
(conj return-items(get-item (:content x)))))

;; finds all feed items and calls the get-author function for each one
;; @param [item] xml-seq the post item which contains the creator
(defn get-authors [ return-authors link]
(for [x (get-feed link)
:when (= :item (:tag x))]
(conj return-authors(get-author (:content x)))))

;; we define a default Value to filterTerm in case it's null
;; then we filter the items so that either the title or the description of the feed contain the filter term and the author equals the authorTerm
(defn filter-items-by-term [term author]
(let [term (or term "")]
(let [author (or author "")]
(fn [[item]]
(and (or
(string/includes?
(simplify-string (apply str (get item :title)))
(simplify-string term))
(string/includes?
(simplify-string (apply str (get item :description)))
(simplify-string term))
)
(string/includes?
(simplify-string (apply str (get item :creator)))
(simplify-string author))
)))))


;; calls the get-items function, in the end it returns an array with all the items
;; items get filtered by filter term
(defn get-feed-data [link & [filterTerm authorTerm]]
(filter (filter-items-by-term filterTerm authorTerm) (get-items [] link))
)

;; calls the get-authors function, in the end it returns an array with all authors
(defn get-feed-authors [link]
(distinct (get-authors [] link)))

;; gets the feed title
(defn get-feed-title [link]
(first (for [x (get-feed link)
:when (= :title (:tag x))]
(first (:content x)))))


helper.clj



(ns rss-feeds-webapp.helper
(:require
[clojure.string :as string]))

;; parses a date string into our desired output format
(defn get-time [date-string]
(.format (java.text.SimpleDateFormat. "dd/MM/yyyy - HH:mm") (new java.util.Date date-string))
)

;; removes all trailing whitespaces and converts the string to lowercase
;; --> needed for accurate comparison
(defn simplify-string [str]
(let [str (or str "")]
(string/lower-case (string/trim str))))

;; takes one string, analyzes it for regex matches of the filterTerm and returns a list with html spans
;; apply list is needed so the lazy sequence gets converted to a list for sequential access
(defn get-highlighted-text-by-key [item filterTerm key]
(let [title []
match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
(if match
;; drops the first element of the match vector, since that's the whole string
(let [nested-title (for [substr (drop 1 match)]
(if (= (simplify-string substr) (simplify-string filterTerm))
[:span :class "highlight" substr]
[:span substr]
)
)]
(apply list (conj title (apply list nested-title) )))
(apply list (conj title [:span (get item key "No title specified")])))))

;; checks if the option value matches the link parameter and returns true
(defn get-selected-option [value filterTerm]
(if (= value filterTerm) true false))


templating.clj



(ns rss-feeds-webapp.templating
(:use
[hiccup.page :only (html5 include-css include-js)]
[hiccup.form :refer :all]
[rss-feeds-webapp.helper :refer :all]
[rss-feeds-webapp.feed :refer :all]
[ring.util.anti-forgery :as util]
)
)

(defn gen-page-head
[title]
[:head
[:title (str title)]
(include-css "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css")
(include-css "/css/styles.css")])



(defn render-item [item filterTerm]
[:div :class "card"
;;[:img :class "card-img-left" :src "..." :alt "Card image cap"]
[:div :class "card-body"
[:h5 (get-highlighted-text-by-key item filterTerm :title)]
[:p :class "card-text" (get item :creator "No author specified")]
[:p :class "card-text" (get-highlighted-text-by-key item filterTerm :description)]
[:p :class "card-date" (get-time (apply str(get item :pubDate "PubDate"))) ]
[:a :class "btn btn-outline-info" :href (apply str(get item :link)) :target "_blank" "Read full article"]
]]
)

(defn render-author-options [item filterTerm]
[:option :value (apply str(get item :creator "No title specified")) :selected (get-selected-option (apply str(get item :creator )) filterTerm )
(get item :creator "No title specified")]
)

(defn render-authors [content filterTerm]
(for [entry content]
(render-author-options (first entry) filterTerm)))

(defn render-feed [content filterTerm]
(for [entry content]
(render-item (first entry) filterTerm)))

(defn render-category-selection []
[:div :class "container margin-top-25 no-left-padding"
[:div :class "col-md-12 no-left-padding"
[:h3 "Or select a category"]
[:form :method "get" :action "/feed" :class "category-form"
(util/anti-forgery-field)
[:div :class "col-lg-8 col-sm-12 no-left-padding no-right-padding"
[:div :class "input-group"
[:div :class "select-wrapper"
[:select :class "custom-select form-control no-right-border-radius" :name (str "link")
[:option :value "https://www.nytimes.com/services/xml/rss/nyt/World.xml" "World News"]
[:option :value "http://feeds.bbci.co.uk/news/world/europe/rss.xml" "Europe"]
[:option :value "http://feeds.nytimes.com/nyt/rss/Business" "Business"]
[:option :value "http://feeds.bbci.co.uk/news/health/rss.xml" "Health"]
[:option :value "http://feeds.nytimes.com/nyt/rss/Technology" "Technology"]
[:option :value "http://rss.nytimes.com/services/xml/rss/nyt/Travel.xml" "Travel"]
[:option :value "http://feeds.bbci.co.uk/news/education/rss.xml" "Education & Family"]
[:option :value "https://www.nytimes.com/services/xml/rss/nyt/Sports.xml" "Sports"]
[:option :value "http://feeds.feedburner.com/vagubunt" "Linux & Webdevelopment"]
[:option :value "https://www.sitepoint.com/feed/" "Programming"]
[:option :value "https://css-tricks.com/feed" "CSS-Tricks"]
[:option :value "https://clojureverse.org/posts.rss" "Clojure"]
]
]
[:span :class "input-group-btn"
[:input.action :type "submit" :class "btn btn-outline-secondary no-left-border-radius" :value "Load selected category"]]]]]]]
)

(defn render-possible-feeds []
[:div :class "container no-left-padding" :style "margin-top: 50px"
[:div :class "col-md-12 no-left-padding"
[:h5 "Possible Feeds"]
[:div :class "col-md-12 no-left-padding"
[:p "http://feeds.feedburner.com/vagubunt"]
[:p "http://www.spiegel.de/schlagzeilen/tops/index.rss"]
[:p "http://rss.cnn.com/rss/cnn_topstories.rss"]
[:p "http://feeds.bbci.co.uk/news/world/europe/rss.xml"]
[:p "https://www.nytimes.com/services/xml/rss/nyt/HomePage.xml"]
[:p "https://www.nytimes.com/services/xml/rss/nyt/World.xml"]
[:p "http://feeds.nytimes.com/nyt/rss/Technology"]
[:p "http://www.tagesschau.de/xml/rss2"]
[:p "https://spectrum.ieee.org/rss/fulltext"]
]]]
)

(defn body [title & content]
(html5
(gen-page-head title)
[:body
[:div :class "container" :style "position: relative"
[:a :href "/" :title "Go back home"
[:img :class "site-logo" :src "http://tiny.cc/rdun3y" :alt "Card image cap"]]
[:h1 :class "margin-vertical-20" title]
content
]]))

;; renders an input group with an input field and an action button to the right
(defn render-input-group [inputPlaceholder, actionText, inputName, inputType]
[:div :class "input-group"
[:input :type inputType :class "form-control" :name inputName :placeholder inputPlaceholder]
[:span :class "input-group-btn"
[:input.action :type "submit" :class "btn btn-primary no-left-border-radius" :value actionText]]]
)

;; HTML Page for /
(defn view-input []
(body
"RSS-Feed Viewer"
[:div
[:div :class "container margin-top-25 no-left-padding"
[:div :class "col-md-12 no-left-padding"
[:h3 "Enter a rss-feed url:"]
[:form :method "get" :action "/feed"
(util/anti-forgery-field)
[:div :class "col-lg-6 no-left-padding no-right-padding"
(render-input-group "Enter your RSS Url..." "load feed" "link" "url")
]
]
]]
(render-category-selection)
(render-possible-feeds)]
))

;; HTML Page for /view-error
(defn view-error []
(body
"RSS-Feed Viewer"
[:div
[:div :class "container margin-top-25 no-left-padding"
[:div :class "col-md-12 no-left-padding"
[:h3 :class "error" "Ups, something went wrong..."]

]]]
))

;; HTML Page for /feed
(defn view-feed [link, filter, author]
(body
(get-feed-title link)
[:div :class "container no-left-padding no-right-padding"
[:form :method "get" :action "/feed"
(util/anti-forgery-field)
[:div :class "col-lg-6 no-left-padding no-right-padding"
[:div :class "input-group hidden"
[:input :type "url" :class "form-control" :name "link" :placeholder "..." :value link]
[:span :class "input-group-btn"
[:input.action :type "submit" :class "btn btn-primary no-left-border-radius" :value "Go"]]]
[:div :class "input-group"
[:input :type "text" :class "form-control" :autofocus true :name "filter" :placeholder "Filter RSS Feeds..." :value filter]
[:span :class "input-group-btn"
[:input.action :type "submit" :class "btn btn-primary no-left-border-radius" :value "Filter feeds"]]]
]
[:div :class "col-lg-8 col-sm-12 no-left-padding no-right-padding"
[:div :class "input-group margin-top-25"
[:div :class "select-wrapper"
[:select :class "custom-select form-control no-right-border-radius" :name (str "author")
[:option :value "" :selected (get-selected-option "" author ) "--- choose author ---" ]
(render-authors (get-feed-authors link) author)]
]
[:span :class "input-group-btn"
[:input.action :type "submit" :class "btn btn-outline-secondary no-left-border-radius" :value "Filter authors"]]]]]
]
(render-feed (get-feed-data link filter author) filter)
))









share|improve this question











$endgroup$




bumped to the homepage by Community 37 mins ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.





















    2












    $begingroup$


    This semester a classmate and I visited a course on functional programming in our university. For grading, we've to submit a small project which should be written in Clojure and make use of functional programming.
    However, the topic of the course itself was mostly about Clojure and its syntax and not so much about functional programming itself. That's why we're not quite sure if our code complies with functional programming paradigms.



    It would be highly appreciated if someone takes a look at our code to point out flaws and possible violations of functional programming.
    If you see anything that could be written simpler or seems confusing to you, please point it out, since we're new to Clojure and not sure if we've done everything correctly or in the most efficient way (probably not).



    Following below is the main part of our code. The entire project can be found in our Gitlab repository.
    Since it's a web app we've used hiccup, ring and compojure.
    The main functionalities are a landing page at /, which lets you either put in a RSS Feed url or select one RSS Feed from categories provided by us.
    Once you submit you're routed to /feed. Here we render the separate items of the current feed.
    You've the possibility to filter the feed by typing something, like 'Trump' and as a result we only show the feed items that include the word 'Trump' either in the title or the description. Additionally those "matches" are afterwards highlighted like so:



    enter image description here



    Furthermore, there's the possibility to filter by author. All authors of the current feed are available as an option in a select box.



    Code
    feed.clj



     (ns rss-feeds-webapp.feed
    (:require
    [compojure.core :refer :all]
    [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
    [clj-http.client :as client]
    [clojure.java.io :as io]
    [clojure.xml :as xml]
    [clojure.string :as string])
    (:import [java.io ByteArrayInputStream])
    (:use
    [rss-feeds-webapp.helper :refer :all]
    ))

    ;; gets the feed from the url, then parses it to an byte array and then to an xml sequenze
    (defn get-feed [link]
    (xml-seq (xml/parse (io/input-stream (ByteArrayInputStream. (.getBytes (:body (client/get link)) "UTF-8"))))) )

    ;; returns the content of the xml tag
    ;; @param [item] xml-seq the post item which contains the title, description and link7
    ;; @param [tag] key the tag, that contains the information you want, e.g. title
    (defn get-content [item tag]
    (for [x item
    :when (= tag (:tag x))]
    (first (:content x))))

    ;; creates an hash-map for the feed item, containing the title, description, link, creator and pubDate (if it exists)
    ;; param [item] xml-seq the post item,
    (defn get-item [item]
    :title (get-content item :title )
    :description (get-content item :description)
    :link (get-content item :link)
    :pubDate (get-content item :pubDate)
    :image (get-content item :image)
    :creator (get-content item :dc:creator)

    )

    ;; creates an hash-map for the feed item, containing the creator
    ;; param [item] xml-seq the post item,
    (defn get-author [item]
    :creator (get-content item :dc:creator)
    )

    ;; finds all feed items and calls the get-item function for each one
    ;; @param [item] xml-seq the post item which contains the title, description, creator and link
    (defn get-items [ return-items link]
    (for [x (get-feed link)
    :when (= :item (:tag x))]
    (conj return-items(get-item (:content x)))))

    ;; finds all feed items and calls the get-author function for each one
    ;; @param [item] xml-seq the post item which contains the creator
    (defn get-authors [ return-authors link]
    (for [x (get-feed link)
    :when (= :item (:tag x))]
    (conj return-authors(get-author (:content x)))))

    ;; we define a default Value to filterTerm in case it's null
    ;; then we filter the items so that either the title or the description of the feed contain the filter term and the author equals the authorTerm
    (defn filter-items-by-term [term author]
    (let [term (or term "")]
    (let [author (or author "")]
    (fn [[item]]
    (and (or
    (string/includes?
    (simplify-string (apply str (get item :title)))
    (simplify-string term))
    (string/includes?
    (simplify-string (apply str (get item :description)))
    (simplify-string term))
    )
    (string/includes?
    (simplify-string (apply str (get item :creator)))
    (simplify-string author))
    )))))


    ;; calls the get-items function, in the end it returns an array with all the items
    ;; items get filtered by filter term
    (defn get-feed-data [link & [filterTerm authorTerm]]
    (filter (filter-items-by-term filterTerm authorTerm) (get-items [] link))
    )

    ;; calls the get-authors function, in the end it returns an array with all authors
    (defn get-feed-authors [link]
    (distinct (get-authors [] link)))

    ;; gets the feed title
    (defn get-feed-title [link]
    (first (for [x (get-feed link)
    :when (= :title (:tag x))]
    (first (:content x)))))


    helper.clj



    (ns rss-feeds-webapp.helper
    (:require
    [clojure.string :as string]))

    ;; parses a date string into our desired output format
    (defn get-time [date-string]
    (.format (java.text.SimpleDateFormat. "dd/MM/yyyy - HH:mm") (new java.util.Date date-string))
    )

    ;; removes all trailing whitespaces and converts the string to lowercase
    ;; --> needed for accurate comparison
    (defn simplify-string [str]
    (let [str (or str "")]
    (string/lower-case (string/trim str))))

    ;; takes one string, analyzes it for regex matches of the filterTerm and returns a list with html spans
    ;; apply list is needed so the lazy sequence gets converted to a list for sequential access
    (defn get-highlighted-text-by-key [item filterTerm key]
    (let [title []
    match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
    (if match
    ;; drops the first element of the match vector, since that's the whole string
    (let [nested-title (for [substr (drop 1 match)]
    (if (= (simplify-string substr) (simplify-string filterTerm))
    [:span :class "highlight" substr]
    [:span substr]
    )
    )]
    (apply list (conj title (apply list nested-title) )))
    (apply list (conj title [:span (get item key "No title specified")])))))

    ;; checks if the option value matches the link parameter and returns true
    (defn get-selected-option [value filterTerm]
    (if (= value filterTerm) true false))


    templating.clj



    (ns rss-feeds-webapp.templating
    (:use
    [hiccup.page :only (html5 include-css include-js)]
    [hiccup.form :refer :all]
    [rss-feeds-webapp.helper :refer :all]
    [rss-feeds-webapp.feed :refer :all]
    [ring.util.anti-forgery :as util]
    )
    )

    (defn gen-page-head
    [title]
    [:head
    [:title (str title)]
    (include-css "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css")
    (include-css "/css/styles.css")])



    (defn render-item [item filterTerm]
    [:div :class "card"
    ;;[:img :class "card-img-left" :src "..." :alt "Card image cap"]
    [:div :class "card-body"
    [:h5 (get-highlighted-text-by-key item filterTerm :title)]
    [:p :class "card-text" (get item :creator "No author specified")]
    [:p :class "card-text" (get-highlighted-text-by-key item filterTerm :description)]
    [:p :class "card-date" (get-time (apply str(get item :pubDate "PubDate"))) ]
    [:a :class "btn btn-outline-info" :href (apply str(get item :link)) :target "_blank" "Read full article"]
    ]]
    )

    (defn render-author-options [item filterTerm]
    [:option :value (apply str(get item :creator "No title specified")) :selected (get-selected-option (apply str(get item :creator )) filterTerm )
    (get item :creator "No title specified")]
    )

    (defn render-authors [content filterTerm]
    (for [entry content]
    (render-author-options (first entry) filterTerm)))

    (defn render-feed [content filterTerm]
    (for [entry content]
    (render-item (first entry) filterTerm)))

    (defn render-category-selection []
    [:div :class "container margin-top-25 no-left-padding"
    [:div :class "col-md-12 no-left-padding"
    [:h3 "Or select a category"]
    [:form :method "get" :action "/feed" :class "category-form"
    (util/anti-forgery-field)
    [:div :class "col-lg-8 col-sm-12 no-left-padding no-right-padding"
    [:div :class "input-group"
    [:div :class "select-wrapper"
    [:select :class "custom-select form-control no-right-border-radius" :name (str "link")
    [:option :value "https://www.nytimes.com/services/xml/rss/nyt/World.xml" "World News"]
    [:option :value "http://feeds.bbci.co.uk/news/world/europe/rss.xml" "Europe"]
    [:option :value "http://feeds.nytimes.com/nyt/rss/Business" "Business"]
    [:option :value "http://feeds.bbci.co.uk/news/health/rss.xml" "Health"]
    [:option :value "http://feeds.nytimes.com/nyt/rss/Technology" "Technology"]
    [:option :value "http://rss.nytimes.com/services/xml/rss/nyt/Travel.xml" "Travel"]
    [:option :value "http://feeds.bbci.co.uk/news/education/rss.xml" "Education & Family"]
    [:option :value "https://www.nytimes.com/services/xml/rss/nyt/Sports.xml" "Sports"]
    [:option :value "http://feeds.feedburner.com/vagubunt" "Linux & Webdevelopment"]
    [:option :value "https://www.sitepoint.com/feed/" "Programming"]
    [:option :value "https://css-tricks.com/feed" "CSS-Tricks"]
    [:option :value "https://clojureverse.org/posts.rss" "Clojure"]
    ]
    ]
    [:span :class "input-group-btn"
    [:input.action :type "submit" :class "btn btn-outline-secondary no-left-border-radius" :value "Load selected category"]]]]]]]
    )

    (defn render-possible-feeds []
    [:div :class "container no-left-padding" :style "margin-top: 50px"
    [:div :class "col-md-12 no-left-padding"
    [:h5 "Possible Feeds"]
    [:div :class "col-md-12 no-left-padding"
    [:p "http://feeds.feedburner.com/vagubunt"]
    [:p "http://www.spiegel.de/schlagzeilen/tops/index.rss"]
    [:p "http://rss.cnn.com/rss/cnn_topstories.rss"]
    [:p "http://feeds.bbci.co.uk/news/world/europe/rss.xml"]
    [:p "https://www.nytimes.com/services/xml/rss/nyt/HomePage.xml"]
    [:p "https://www.nytimes.com/services/xml/rss/nyt/World.xml"]
    [:p "http://feeds.nytimes.com/nyt/rss/Technology"]
    [:p "http://www.tagesschau.de/xml/rss2"]
    [:p "https://spectrum.ieee.org/rss/fulltext"]
    ]]]
    )

    (defn body [title & content]
    (html5
    (gen-page-head title)
    [:body
    [:div :class "container" :style "position: relative"
    [:a :href "/" :title "Go back home"
    [:img :class "site-logo" :src "http://tiny.cc/rdun3y" :alt "Card image cap"]]
    [:h1 :class "margin-vertical-20" title]
    content
    ]]))

    ;; renders an input group with an input field and an action button to the right
    (defn render-input-group [inputPlaceholder, actionText, inputName, inputType]
    [:div :class "input-group"
    [:input :type inputType :class "form-control" :name inputName :placeholder inputPlaceholder]
    [:span :class "input-group-btn"
    [:input.action :type "submit" :class "btn btn-primary no-left-border-radius" :value actionText]]]
    )

    ;; HTML Page for /
    (defn view-input []
    (body
    "RSS-Feed Viewer"
    [:div
    [:div :class "container margin-top-25 no-left-padding"
    [:div :class "col-md-12 no-left-padding"
    [:h3 "Enter a rss-feed url:"]
    [:form :method "get" :action "/feed"
    (util/anti-forgery-field)
    [:div :class "col-lg-6 no-left-padding no-right-padding"
    (render-input-group "Enter your RSS Url..." "load feed" "link" "url")
    ]
    ]
    ]]
    (render-category-selection)
    (render-possible-feeds)]
    ))

    ;; HTML Page for /view-error
    (defn view-error []
    (body
    "RSS-Feed Viewer"
    [:div
    [:div :class "container margin-top-25 no-left-padding"
    [:div :class "col-md-12 no-left-padding"
    [:h3 :class "error" "Ups, something went wrong..."]

    ]]]
    ))

    ;; HTML Page for /feed
    (defn view-feed [link, filter, author]
    (body
    (get-feed-title link)
    [:div :class "container no-left-padding no-right-padding"
    [:form :method "get" :action "/feed"
    (util/anti-forgery-field)
    [:div :class "col-lg-6 no-left-padding no-right-padding"
    [:div :class "input-group hidden"
    [:input :type "url" :class "form-control" :name "link" :placeholder "..." :value link]
    [:span :class "input-group-btn"
    [:input.action :type "submit" :class "btn btn-primary no-left-border-radius" :value "Go"]]]
    [:div :class "input-group"
    [:input :type "text" :class "form-control" :autofocus true :name "filter" :placeholder "Filter RSS Feeds..." :value filter]
    [:span :class "input-group-btn"
    [:input.action :type "submit" :class "btn btn-primary no-left-border-radius" :value "Filter feeds"]]]
    ]
    [:div :class "col-lg-8 col-sm-12 no-left-padding no-right-padding"
    [:div :class "input-group margin-top-25"
    [:div :class "select-wrapper"
    [:select :class "custom-select form-control no-right-border-radius" :name (str "author")
    [:option :value "" :selected (get-selected-option "" author ) "--- choose author ---" ]
    (render-authors (get-feed-authors link) author)]
    ]
    [:span :class "input-group-btn"
    [:input.action :type "submit" :class "btn btn-outline-secondary no-left-border-radius" :value "Filter authors"]]]]]
    ]
    (render-feed (get-feed-data link filter author) filter)
    ))









    share|improve this question











    $endgroup$




    bumped to the homepage by Community 37 mins ago


    This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.

















      2












      2








      2





      $begingroup$


      This semester a classmate and I visited a course on functional programming in our university. For grading, we've to submit a small project which should be written in Clojure and make use of functional programming.
      However, the topic of the course itself was mostly about Clojure and its syntax and not so much about functional programming itself. That's why we're not quite sure if our code complies with functional programming paradigms.



      It would be highly appreciated if someone takes a look at our code to point out flaws and possible violations of functional programming.
      If you see anything that could be written simpler or seems confusing to you, please point it out, since we're new to Clojure and not sure if we've done everything correctly or in the most efficient way (probably not).



      Following below is the main part of our code. The entire project can be found in our Gitlab repository.
      Since it's a web app we've used hiccup, ring and compojure.
      The main functionalities are a landing page at /, which lets you either put in a RSS Feed url or select one RSS Feed from categories provided by us.
      Once you submit you're routed to /feed. Here we render the separate items of the current feed.
      You've the possibility to filter the feed by typing something, like 'Trump' and as a result we only show the feed items that include the word 'Trump' either in the title or the description. Additionally those "matches" are afterwards highlighted like so:



      enter image description here



      Furthermore, there's the possibility to filter by author. All authors of the current feed are available as an option in a select box.



      Code
      feed.clj



       (ns rss-feeds-webapp.feed
      (:require
      [compojure.core :refer :all]
      [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
      [clj-http.client :as client]
      [clojure.java.io :as io]
      [clojure.xml :as xml]
      [clojure.string :as string])
      (:import [java.io ByteArrayInputStream])
      (:use
      [rss-feeds-webapp.helper :refer :all]
      ))

      ;; gets the feed from the url, then parses it to an byte array and then to an xml sequenze
      (defn get-feed [link]
      (xml-seq (xml/parse (io/input-stream (ByteArrayInputStream. (.getBytes (:body (client/get link)) "UTF-8"))))) )

      ;; returns the content of the xml tag
      ;; @param [item] xml-seq the post item which contains the title, description and link7
      ;; @param [tag] key the tag, that contains the information you want, e.g. title
      (defn get-content [item tag]
      (for [x item
      :when (= tag (:tag x))]
      (first (:content x))))

      ;; creates an hash-map for the feed item, containing the title, description, link, creator and pubDate (if it exists)
      ;; param [item] xml-seq the post item,
      (defn get-item [item]
      :title (get-content item :title )
      :description (get-content item :description)
      :link (get-content item :link)
      :pubDate (get-content item :pubDate)
      :image (get-content item :image)
      :creator (get-content item :dc:creator)

      )

      ;; creates an hash-map for the feed item, containing the creator
      ;; param [item] xml-seq the post item,
      (defn get-author [item]
      :creator (get-content item :dc:creator)
      )

      ;; finds all feed items and calls the get-item function for each one
      ;; @param [item] xml-seq the post item which contains the title, description, creator and link
      (defn get-items [ return-items link]
      (for [x (get-feed link)
      :when (= :item (:tag x))]
      (conj return-items(get-item (:content x)))))

      ;; finds all feed items and calls the get-author function for each one
      ;; @param [item] xml-seq the post item which contains the creator
      (defn get-authors [ return-authors link]
      (for [x (get-feed link)
      :when (= :item (:tag x))]
      (conj return-authors(get-author (:content x)))))

      ;; we define a default Value to filterTerm in case it's null
      ;; then we filter the items so that either the title or the description of the feed contain the filter term and the author equals the authorTerm
      (defn filter-items-by-term [term author]
      (let [term (or term "")]
      (let [author (or author "")]
      (fn [[item]]
      (and (or
      (string/includes?
      (simplify-string (apply str (get item :title)))
      (simplify-string term))
      (string/includes?
      (simplify-string (apply str (get item :description)))
      (simplify-string term))
      )
      (string/includes?
      (simplify-string (apply str (get item :creator)))
      (simplify-string author))
      )))))


      ;; calls the get-items function, in the end it returns an array with all the items
      ;; items get filtered by filter term
      (defn get-feed-data [link & [filterTerm authorTerm]]
      (filter (filter-items-by-term filterTerm authorTerm) (get-items [] link))
      )

      ;; calls the get-authors function, in the end it returns an array with all authors
      (defn get-feed-authors [link]
      (distinct (get-authors [] link)))

      ;; gets the feed title
      (defn get-feed-title [link]
      (first (for [x (get-feed link)
      :when (= :title (:tag x))]
      (first (:content x)))))


      helper.clj



      (ns rss-feeds-webapp.helper
      (:require
      [clojure.string :as string]))

      ;; parses a date string into our desired output format
      (defn get-time [date-string]
      (.format (java.text.SimpleDateFormat. "dd/MM/yyyy - HH:mm") (new java.util.Date date-string))
      )

      ;; removes all trailing whitespaces and converts the string to lowercase
      ;; --> needed for accurate comparison
      (defn simplify-string [str]
      (let [str (or str "")]
      (string/lower-case (string/trim str))))

      ;; takes one string, analyzes it for regex matches of the filterTerm and returns a list with html spans
      ;; apply list is needed so the lazy sequence gets converted to a list for sequential access
      (defn get-highlighted-text-by-key [item filterTerm key]
      (let [title []
      match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
      (if match
      ;; drops the first element of the match vector, since that's the whole string
      (let [nested-title (for [substr (drop 1 match)]
      (if (= (simplify-string substr) (simplify-string filterTerm))
      [:span :class "highlight" substr]
      [:span substr]
      )
      )]
      (apply list (conj title (apply list nested-title) )))
      (apply list (conj title [:span (get item key "No title specified")])))))

      ;; checks if the option value matches the link parameter and returns true
      (defn get-selected-option [value filterTerm]
      (if (= value filterTerm) true false))


      templating.clj



      (ns rss-feeds-webapp.templating
      (:use
      [hiccup.page :only (html5 include-css include-js)]
      [hiccup.form :refer :all]
      [rss-feeds-webapp.helper :refer :all]
      [rss-feeds-webapp.feed :refer :all]
      [ring.util.anti-forgery :as util]
      )
      )

      (defn gen-page-head
      [title]
      [:head
      [:title (str title)]
      (include-css "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css")
      (include-css "/css/styles.css")])



      (defn render-item [item filterTerm]
      [:div :class "card"
      ;;[:img :class "card-img-left" :src "..." :alt "Card image cap"]
      [:div :class "card-body"
      [:h5 (get-highlighted-text-by-key item filterTerm :title)]
      [:p :class "card-text" (get item :creator "No author specified")]
      [:p :class "card-text" (get-highlighted-text-by-key item filterTerm :description)]
      [:p :class "card-date" (get-time (apply str(get item :pubDate "PubDate"))) ]
      [:a :class "btn btn-outline-info" :href (apply str(get item :link)) :target "_blank" "Read full article"]
      ]]
      )

      (defn render-author-options [item filterTerm]
      [:option :value (apply str(get item :creator "No title specified")) :selected (get-selected-option (apply str(get item :creator )) filterTerm )
      (get item :creator "No title specified")]
      )

      (defn render-authors [content filterTerm]
      (for [entry content]
      (render-author-options (first entry) filterTerm)))

      (defn render-feed [content filterTerm]
      (for [entry content]
      (render-item (first entry) filterTerm)))

      (defn render-category-selection []
      [:div :class "container margin-top-25 no-left-padding"
      [:div :class "col-md-12 no-left-padding"
      [:h3 "Or select a category"]
      [:form :method "get" :action "/feed" :class "category-form"
      (util/anti-forgery-field)
      [:div :class "col-lg-8 col-sm-12 no-left-padding no-right-padding"
      [:div :class "input-group"
      [:div :class "select-wrapper"
      [:select :class "custom-select form-control no-right-border-radius" :name (str "link")
      [:option :value "https://www.nytimes.com/services/xml/rss/nyt/World.xml" "World News"]
      [:option :value "http://feeds.bbci.co.uk/news/world/europe/rss.xml" "Europe"]
      [:option :value "http://feeds.nytimes.com/nyt/rss/Business" "Business"]
      [:option :value "http://feeds.bbci.co.uk/news/health/rss.xml" "Health"]
      [:option :value "http://feeds.nytimes.com/nyt/rss/Technology" "Technology"]
      [:option :value "http://rss.nytimes.com/services/xml/rss/nyt/Travel.xml" "Travel"]
      [:option :value "http://feeds.bbci.co.uk/news/education/rss.xml" "Education & Family"]
      [:option :value "https://www.nytimes.com/services/xml/rss/nyt/Sports.xml" "Sports"]
      [:option :value "http://feeds.feedburner.com/vagubunt" "Linux & Webdevelopment"]
      [:option :value "https://www.sitepoint.com/feed/" "Programming"]
      [:option :value "https://css-tricks.com/feed" "CSS-Tricks"]
      [:option :value "https://clojureverse.org/posts.rss" "Clojure"]
      ]
      ]
      [:span :class "input-group-btn"
      [:input.action :type "submit" :class "btn btn-outline-secondary no-left-border-radius" :value "Load selected category"]]]]]]]
      )

      (defn render-possible-feeds []
      [:div :class "container no-left-padding" :style "margin-top: 50px"
      [:div :class "col-md-12 no-left-padding"
      [:h5 "Possible Feeds"]
      [:div :class "col-md-12 no-left-padding"
      [:p "http://feeds.feedburner.com/vagubunt"]
      [:p "http://www.spiegel.de/schlagzeilen/tops/index.rss"]
      [:p "http://rss.cnn.com/rss/cnn_topstories.rss"]
      [:p "http://feeds.bbci.co.uk/news/world/europe/rss.xml"]
      [:p "https://www.nytimes.com/services/xml/rss/nyt/HomePage.xml"]
      [:p "https://www.nytimes.com/services/xml/rss/nyt/World.xml"]
      [:p "http://feeds.nytimes.com/nyt/rss/Technology"]
      [:p "http://www.tagesschau.de/xml/rss2"]
      [:p "https://spectrum.ieee.org/rss/fulltext"]
      ]]]
      )

      (defn body [title & content]
      (html5
      (gen-page-head title)
      [:body
      [:div :class "container" :style "position: relative"
      [:a :href "/" :title "Go back home"
      [:img :class "site-logo" :src "http://tiny.cc/rdun3y" :alt "Card image cap"]]
      [:h1 :class "margin-vertical-20" title]
      content
      ]]))

      ;; renders an input group with an input field and an action button to the right
      (defn render-input-group [inputPlaceholder, actionText, inputName, inputType]
      [:div :class "input-group"
      [:input :type inputType :class "form-control" :name inputName :placeholder inputPlaceholder]
      [:span :class "input-group-btn"
      [:input.action :type "submit" :class "btn btn-primary no-left-border-radius" :value actionText]]]
      )

      ;; HTML Page for /
      (defn view-input []
      (body
      "RSS-Feed Viewer"
      [:div
      [:div :class "container margin-top-25 no-left-padding"
      [:div :class "col-md-12 no-left-padding"
      [:h3 "Enter a rss-feed url:"]
      [:form :method "get" :action "/feed"
      (util/anti-forgery-field)
      [:div :class "col-lg-6 no-left-padding no-right-padding"
      (render-input-group "Enter your RSS Url..." "load feed" "link" "url")
      ]
      ]
      ]]
      (render-category-selection)
      (render-possible-feeds)]
      ))

      ;; HTML Page for /view-error
      (defn view-error []
      (body
      "RSS-Feed Viewer"
      [:div
      [:div :class "container margin-top-25 no-left-padding"
      [:div :class "col-md-12 no-left-padding"
      [:h3 :class "error" "Ups, something went wrong..."]

      ]]]
      ))

      ;; HTML Page for /feed
      (defn view-feed [link, filter, author]
      (body
      (get-feed-title link)
      [:div :class "container no-left-padding no-right-padding"
      [:form :method "get" :action "/feed"
      (util/anti-forgery-field)
      [:div :class "col-lg-6 no-left-padding no-right-padding"
      [:div :class "input-group hidden"
      [:input :type "url" :class "form-control" :name "link" :placeholder "..." :value link]
      [:span :class "input-group-btn"
      [:input.action :type "submit" :class "btn btn-primary no-left-border-radius" :value "Go"]]]
      [:div :class "input-group"
      [:input :type "text" :class "form-control" :autofocus true :name "filter" :placeholder "Filter RSS Feeds..." :value filter]
      [:span :class "input-group-btn"
      [:input.action :type "submit" :class "btn btn-primary no-left-border-radius" :value "Filter feeds"]]]
      ]
      [:div :class "col-lg-8 col-sm-12 no-left-padding no-right-padding"
      [:div :class "input-group margin-top-25"
      [:div :class "select-wrapper"
      [:select :class "custom-select form-control no-right-border-radius" :name (str "author")
      [:option :value "" :selected (get-selected-option "" author ) "--- choose author ---" ]
      (render-authors (get-feed-authors link) author)]
      ]
      [:span :class "input-group-btn"
      [:input.action :type "submit" :class "btn btn-outline-secondary no-left-border-radius" :value "Filter authors"]]]]]
      ]
      (render-feed (get-feed-data link filter author) filter)
      ))









      share|improve this question











      $endgroup$




      This semester a classmate and I visited a course on functional programming in our university. For grading, we've to submit a small project which should be written in Clojure and make use of functional programming.
      However, the topic of the course itself was mostly about Clojure and its syntax and not so much about functional programming itself. That's why we're not quite sure if our code complies with functional programming paradigms.



      It would be highly appreciated if someone takes a look at our code to point out flaws and possible violations of functional programming.
      If you see anything that could be written simpler or seems confusing to you, please point it out, since we're new to Clojure and not sure if we've done everything correctly or in the most efficient way (probably not).



      Following below is the main part of our code. The entire project can be found in our Gitlab repository.
      Since it's a web app we've used hiccup, ring and compojure.
      The main functionalities are a landing page at /, which lets you either put in a RSS Feed url or select one RSS Feed from categories provided by us.
      Once you submit you're routed to /feed. Here we render the separate items of the current feed.
      You've the possibility to filter the feed by typing something, like 'Trump' and as a result we only show the feed items that include the word 'Trump' either in the title or the description. Additionally those "matches" are afterwards highlighted like so:



      enter image description here



      Furthermore, there's the possibility to filter by author. All authors of the current feed are available as an option in a select box.



      Code
      feed.clj



       (ns rss-feeds-webapp.feed
      (:require
      [compojure.core :refer :all]
      [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
      [clj-http.client :as client]
      [clojure.java.io :as io]
      [clojure.xml :as xml]
      [clojure.string :as string])
      (:import [java.io ByteArrayInputStream])
      (:use
      [rss-feeds-webapp.helper :refer :all]
      ))

      ;; gets the feed from the url, then parses it to an byte array and then to an xml sequenze
      (defn get-feed [link]
      (xml-seq (xml/parse (io/input-stream (ByteArrayInputStream. (.getBytes (:body (client/get link)) "UTF-8"))))) )

      ;; returns the content of the xml tag
      ;; @param [item] xml-seq the post item which contains the title, description and link7
      ;; @param [tag] key the tag, that contains the information you want, e.g. title
      (defn get-content [item tag]
      (for [x item
      :when (= tag (:tag x))]
      (first (:content x))))

      ;; creates an hash-map for the feed item, containing the title, description, link, creator and pubDate (if it exists)
      ;; param [item] xml-seq the post item,
      (defn get-item [item]
      :title (get-content item :title )
      :description (get-content item :description)
      :link (get-content item :link)
      :pubDate (get-content item :pubDate)
      :image (get-content item :image)
      :creator (get-content item :dc:creator)

      )

      ;; creates an hash-map for the feed item, containing the creator
      ;; param [item] xml-seq the post item,
      (defn get-author [item]
      :creator (get-content item :dc:creator)
      )

      ;; finds all feed items and calls the get-item function for each one
      ;; @param [item] xml-seq the post item which contains the title, description, creator and link
      (defn get-items [ return-items link]
      (for [x (get-feed link)
      :when (= :item (:tag x))]
      (conj return-items(get-item (:content x)))))

      ;; finds all feed items and calls the get-author function for each one
      ;; @param [item] xml-seq the post item which contains the creator
      (defn get-authors [ return-authors link]
      (for [x (get-feed link)
      :when (= :item (:tag x))]
      (conj return-authors(get-author (:content x)))))

      ;; we define a default Value to filterTerm in case it's null
      ;; then we filter the items so that either the title or the description of the feed contain the filter term and the author equals the authorTerm
      (defn filter-items-by-term [term author]
      (let [term (or term "")]
      (let [author (or author "")]
      (fn [[item]]
      (and (or
      (string/includes?
      (simplify-string (apply str (get item :title)))
      (simplify-string term))
      (string/includes?
      (simplify-string (apply str (get item :description)))
      (simplify-string term))
      )
      (string/includes?
      (simplify-string (apply str (get item :creator)))
      (simplify-string author))
      )))))


      ;; calls the get-items function, in the end it returns an array with all the items
      ;; items get filtered by filter term
      (defn get-feed-data [link & [filterTerm authorTerm]]
      (filter (filter-items-by-term filterTerm authorTerm) (get-items [] link))
      )

      ;; calls the get-authors function, in the end it returns an array with all authors
      (defn get-feed-authors [link]
      (distinct (get-authors [] link)))

      ;; gets the feed title
      (defn get-feed-title [link]
      (first (for [x (get-feed link)
      :when (= :title (:tag x))]
      (first (:content x)))))


      helper.clj



      (ns rss-feeds-webapp.helper
      (:require
      [clojure.string :as string]))

      ;; parses a date string into our desired output format
      (defn get-time [date-string]
      (.format (java.text.SimpleDateFormat. "dd/MM/yyyy - HH:mm") (new java.util.Date date-string))
      )

      ;; removes all trailing whitespaces and converts the string to lowercase
      ;; --> needed for accurate comparison
      (defn simplify-string [str]
      (let [str (or str "")]
      (string/lower-case (string/trim str))))

      ;; takes one string, analyzes it for regex matches of the filterTerm and returns a list with html spans
      ;; apply list is needed so the lazy sequence gets converted to a list for sequential access
      (defn get-highlighted-text-by-key [item filterTerm key]
      (let [title []
      match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
      (if match
      ;; drops the first element of the match vector, since that's the whole string
      (let [nested-title (for [substr (drop 1 match)]
      (if (= (simplify-string substr) (simplify-string filterTerm))
      [:span :class "highlight" substr]
      [:span substr]
      )
      )]
      (apply list (conj title (apply list nested-title) )))
      (apply list (conj title [:span (get item key "No title specified")])))))

      ;; checks if the option value matches the link parameter and returns true
      (defn get-selected-option [value filterTerm]
      (if (= value filterTerm) true false))


      templating.clj



      (ns rss-feeds-webapp.templating
      (:use
      [hiccup.page :only (html5 include-css include-js)]
      [hiccup.form :refer :all]
      [rss-feeds-webapp.helper :refer :all]
      [rss-feeds-webapp.feed :refer :all]
      [ring.util.anti-forgery :as util]
      )
      )

      (defn gen-page-head
      [title]
      [:head
      [:title (str title)]
      (include-css "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css")
      (include-css "/css/styles.css")])



      (defn render-item [item filterTerm]
      [:div :class "card"
      ;;[:img :class "card-img-left" :src "..." :alt "Card image cap"]
      [:div :class "card-body"
      [:h5 (get-highlighted-text-by-key item filterTerm :title)]
      [:p :class "card-text" (get item :creator "No author specified")]
      [:p :class "card-text" (get-highlighted-text-by-key item filterTerm :description)]
      [:p :class "card-date" (get-time (apply str(get item :pubDate "PubDate"))) ]
      [:a :class "btn btn-outline-info" :href (apply str(get item :link)) :target "_blank" "Read full article"]
      ]]
      )

      (defn render-author-options [item filterTerm]
      [:option :value (apply str(get item :creator "No title specified")) :selected (get-selected-option (apply str(get item :creator )) filterTerm )
      (get item :creator "No title specified")]
      )

      (defn render-authors [content filterTerm]
      (for [entry content]
      (render-author-options (first entry) filterTerm)))

      (defn render-feed [content filterTerm]
      (for [entry content]
      (render-item (first entry) filterTerm)))

      (defn render-category-selection []
      [:div :class "container margin-top-25 no-left-padding"
      [:div :class "col-md-12 no-left-padding"
      [:h3 "Or select a category"]
      [:form :method "get" :action "/feed" :class "category-form"
      (util/anti-forgery-field)
      [:div :class "col-lg-8 col-sm-12 no-left-padding no-right-padding"
      [:div :class "input-group"
      [:div :class "select-wrapper"
      [:select :class "custom-select form-control no-right-border-radius" :name (str "link")
      [:option :value "https://www.nytimes.com/services/xml/rss/nyt/World.xml" "World News"]
      [:option :value "http://feeds.bbci.co.uk/news/world/europe/rss.xml" "Europe"]
      [:option :value "http://feeds.nytimes.com/nyt/rss/Business" "Business"]
      [:option :value "http://feeds.bbci.co.uk/news/health/rss.xml" "Health"]
      [:option :value "http://feeds.nytimes.com/nyt/rss/Technology" "Technology"]
      [:option :value "http://rss.nytimes.com/services/xml/rss/nyt/Travel.xml" "Travel"]
      [:option :value "http://feeds.bbci.co.uk/news/education/rss.xml" "Education & Family"]
      [:option :value "https://www.nytimes.com/services/xml/rss/nyt/Sports.xml" "Sports"]
      [:option :value "http://feeds.feedburner.com/vagubunt" "Linux & Webdevelopment"]
      [:option :value "https://www.sitepoint.com/feed/" "Programming"]
      [:option :value "https://css-tricks.com/feed" "CSS-Tricks"]
      [:option :value "https://clojureverse.org/posts.rss" "Clojure"]
      ]
      ]
      [:span :class "input-group-btn"
      [:input.action :type "submit" :class "btn btn-outline-secondary no-left-border-radius" :value "Load selected category"]]]]]]]
      )

      (defn render-possible-feeds []
      [:div :class "container no-left-padding" :style "margin-top: 50px"
      [:div :class "col-md-12 no-left-padding"
      [:h5 "Possible Feeds"]
      [:div :class "col-md-12 no-left-padding"
      [:p "http://feeds.feedburner.com/vagubunt"]
      [:p "http://www.spiegel.de/schlagzeilen/tops/index.rss"]
      [:p "http://rss.cnn.com/rss/cnn_topstories.rss"]
      [:p "http://feeds.bbci.co.uk/news/world/europe/rss.xml"]
      [:p "https://www.nytimes.com/services/xml/rss/nyt/HomePage.xml"]
      [:p "https://www.nytimes.com/services/xml/rss/nyt/World.xml"]
      [:p "http://feeds.nytimes.com/nyt/rss/Technology"]
      [:p "http://www.tagesschau.de/xml/rss2"]
      [:p "https://spectrum.ieee.org/rss/fulltext"]
      ]]]
      )

      (defn body [title & content]
      (html5
      (gen-page-head title)
      [:body
      [:div :class "container" :style "position: relative"
      [:a :href "/" :title "Go back home"
      [:img :class "site-logo" :src "http://tiny.cc/rdun3y" :alt "Card image cap"]]
      [:h1 :class "margin-vertical-20" title]
      content
      ]]))

      ;; renders an input group with an input field and an action button to the right
      (defn render-input-group [inputPlaceholder, actionText, inputName, inputType]
      [:div :class "input-group"
      [:input :type inputType :class "form-control" :name inputName :placeholder inputPlaceholder]
      [:span :class "input-group-btn"
      [:input.action :type "submit" :class "btn btn-primary no-left-border-radius" :value actionText]]]
      )

      ;; HTML Page for /
      (defn view-input []
      (body
      "RSS-Feed Viewer"
      [:div
      [:div :class "container margin-top-25 no-left-padding"
      [:div :class "col-md-12 no-left-padding"
      [:h3 "Enter a rss-feed url:"]
      [:form :method "get" :action "/feed"
      (util/anti-forgery-field)
      [:div :class "col-lg-6 no-left-padding no-right-padding"
      (render-input-group "Enter your RSS Url..." "load feed" "link" "url")
      ]
      ]
      ]]
      (render-category-selection)
      (render-possible-feeds)]
      ))

      ;; HTML Page for /view-error
      (defn view-error []
      (body
      "RSS-Feed Viewer"
      [:div
      [:div :class "container margin-top-25 no-left-padding"
      [:div :class "col-md-12 no-left-padding"
      [:h3 :class "error" "Ups, something went wrong..."]

      ]]]
      ))

      ;; HTML Page for /feed
      (defn view-feed [link, filter, author]
      (body
      (get-feed-title link)
      [:div :class "container no-left-padding no-right-padding"
      [:form :method "get" :action "/feed"
      (util/anti-forgery-field)
      [:div :class "col-lg-6 no-left-padding no-right-padding"
      [:div :class "input-group hidden"
      [:input :type "url" :class "form-control" :name "link" :placeholder "..." :value link]
      [:span :class "input-group-btn"
      [:input.action :type "submit" :class "btn btn-primary no-left-border-radius" :value "Go"]]]
      [:div :class "input-group"
      [:input :type "text" :class "form-control" :autofocus true :name "filter" :placeholder "Filter RSS Feeds..." :value filter]
      [:span :class "input-group-btn"
      [:input.action :type "submit" :class "btn btn-primary no-left-border-radius" :value "Filter feeds"]]]
      ]
      [:div :class "col-lg-8 col-sm-12 no-left-padding no-right-padding"
      [:div :class "input-group margin-top-25"
      [:div :class "select-wrapper"
      [:select :class "custom-select form-control no-right-border-radius" :name (str "author")
      [:option :value "" :selected (get-selected-option "" author ) "--- choose author ---" ]
      (render-authors (get-feed-authors link) author)]
      ]
      [:span :class "input-group-btn"
      [:input.action :type "submit" :class "btn btn-outline-secondary no-left-border-radius" :value "Filter authors"]]]]]
      ]
      (render-feed (get-feed-data link filter author) filter)
      ))






      functional-programming homework clojure rss






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Mar 4 at 23:38









      200_success

      131k17157422




      131k17157422










      asked Mar 4 at 21:18









      MarioMario

      111




      111





      bumped to the homepage by Community 37 mins ago


      This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.







      bumped to the homepage by Community 37 mins ago


      This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.






















          2 Answers
          2






          active

          oldest

          votes


















          0












          $begingroup$

          Thanks a lot for the thorough explanation and analysis!



          I tried your suggestion for updating the 'get-highlighted-text-by-key' method, unfortunately, it didn't work right away though. I managed to make it work like so:



          (defn get-highlighted-text-by-key [item filterTerm key]
          (if-let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))
          (apply str (get item key)))]
          ;; drops the first element of the match vector, since that's the whole string
          (let [nested-title (for [substr (drop 1 match)]
          (if (= (simplify-string substr) (simplify-string filterTerm))
          [:span :class "highlight" substr]
          [:span substr]
          )
          )]
          nested-title)
          [:span (get item key "No title specified")]))


          This is also way simpler and makes more sense. When I wrote the method I had the feeling that something is off, but I couldn't quite wrap my head around it. So thanks for clearing that up!






          share|improve this answer











          $endgroup$












          • $begingroup$
            No problem. Glad to help. Note though, I'm not sure that this should be an answer. It might get flagged.
            $endgroup$
            – Carcigenicate
            Mar 5 at 20:42










          • $begingroup$
            Yes, I tried posting as a comment, but I didn't manage to make it work for the code though :/
            $endgroup$
            – Mario
            Mar 5 at 20:43



















          0












          $begingroup$

          This code is quite good for someone learning the language. You haven't made any of the common errors such as using def inside of function instead of let. I don't see anything really outright wrong with the code, so I'll just be commenting on style and things that I think can be improved. I'll just go top to bottom after addressing some things you mentioned.





          . . . we're not quite sure if our code complies with functional programming paradigms.




          Yes, this code is functional. You aren't needlessly mutating anything by abusing atoms or relying on side-effects. Really, those are major deciding factors.




          (:use [rss-feeds-webapp.helper :refer :all])


          Shouldn't be there. That should be tucked inside the :require with everything else. Use of :use is discouraged, as is :refer :all in many cases. Blanket unqualified imports of namespaces aren't ideal. Say you come back to this project a year from now. Are you going to remember what functions come from what modules? Unless the names provide enough context, you may have trouble. Bulk unqualified imports also increase the chance of having a name conflict. Always try to use :as, or :refer [...] when importing. That way you can easily see what code is coming from where, and avoid polluting your namespace.




          (.getBytes (:body (client/get link)) "UTF-8")


          That would benefit from using a type hint.



          ; I'm assuming :body is returning a String
          (ByteArrayInputStream. ^String (.getBytes (:body (client/get link)) "UTF-8"))


          Not only does that help a smart IDE like IntelliJ give you better autocomplete suggestions, it also makes your code faster by avoiding reflection. If you run lein check, your call to .getBytes will cause the following warning:



          Reflection warning, your-file-here.clj - reference to field getBytes can't be resolved.


          Avoiding reflection isn't a big deal in this case, but it's a good thing to keep in mind.




          get-content and similar functions make good use of for. I'll just point out though that they can also be written in terms of map and filter as well:



          (defn get-content [item tag]
          (->> item
          (filter #(= tag (:tag %)))
          (map #(first (:content %)))))

          ; Or

          (defn get-content [item tag]
          (->> item
          (filter #(= tag (:tag %)))
          (map (comp first :content))))


          If you already have a ->> "pipe" going, it may prove to be cleaner to use map/filter here instead of for. This is purely a matter of taste though.




          (defn filter-items-by-term [term author]
          (let [term (or term "")]
          (let [author (or author "")]


          contains needless nesting. It can simply be



          (defn filter-items-by-term [term author]
          (let [term (or term "")
          author (or author "")]



          A lot of your code (like in the example immediately above), you're doing something like



          (or some-parameter a-default-value)


          Now, this is a good way of dealing with possibly-nil values. The placement of the check is weird though. Take a look at filter-items-by-term. Why are term and author possibly nil? Because get-feed-data takes optional parameters, and passes the data, unchecked, to filter-items-by-term. This means that part of the implementation of filter-items-by-term (checking for nil values) is needed due to the implementation of get-feed-data (passing potentially nil values). What if you change how one of the functions work and forget to change the other? It also seems needlessly complicated that many of your functions are trying to "protect themselves" by assuming that bad data may be handed in. It would be much cleaner to expect that the caller, when possible, checks the data prior to calling. All your functions should expect valid data. If the caller has bad data, it's up to them to fix it. I'd make the following changes:



          (defn filter-items-by-term [term author]
          ; Assumes valid data is being passed!
          (fn [[item]]

          (defn get-feed-data [link & [filterTerm authorTerm]]
          ; It's now this function's duty to verify its own data
          (filter (filter-items-by-term (or filterTerm "")
          (or authorTerm ""))
          (get-items [] link)))


          And similarly for the other cases like this.



          I also cleaned up get-feed-data. It had two things that I didn't like:



          • Trailing ) on a new line, like you would have a } in Java. This isn't necessary if you're using a good IDE and consistent indentation. It seems like one of you is using this style while the other isn't, since it's inconsistent. Even worse than using an unidiomatic style is applying the style inconsistently. Your teacher will mark you down for inconsistency alone if they're paying attention.


          • Trying to shove a bunch on one line. It's much better to break up long lines for readability.



          Also in that same function, you have the long anonymous function being returned. In it, you repeatedly access item using keys. It might be cleaner to deconstruct item in the parameter list:



          (fn [[:keys [title description creator]]]
          (and (or
          (string/includes?
          (simplify-string (apply str title))
          (simplify-string term))
          (string/includes?
          (simplify-string (apply str description))
          (simplify-string term)))

          (string/includes?
          (simplify-string (apply str creator))
          (simplify-string author))))


          Also note, (get item :title) can be written simply as (:title item). Keywords implement IFn:



          (ifn? :hello)
          => true


          They are usable as functions that return the corresponding element when applied to a map.




          get-feed-data could also make use of partial. You're having the function return another function so you can supply some data ahead of time, then have filter call it with the last bit of data. This is very common, and is the situation that partial was made for. Id make the following changes:



          ; Note how "item" was simply added as a third parameter
          (defn filter-items-by-term [term author :keys [title description creator]]
          ; And now a function isn't returned
          (and (or
          (string/includes?
          (simplify-string (apply str title))
          (simplify-string term))
          (string/includes?
          (simplify-string (apply str description))
          (simplify-string term)))

          (string/includes?
          (simplify-string (apply str creator))
          (simplify-string author))))


          And now:



          (defn get-feed-data [link & [filterTerm authorTerm]]
          ; Partially apply "filterTerm" and "autherTerm" to "filter-items-by-term"
          (filter (partial filter-items-by-term filterTerm authorTerm)
          (get-items [] link)))


          or, just manually wrap it in another function (#() here):



          (defn get-feed-data [link & [filterTerm authorTerm]]
          (filter #(filter-items-by-term filterTerm authorTerm %)
          (get-items [] link)))


          partial may seem complicated at first, but this example should make it clearer:



          (let [add-five (partial + 5)]
          (println (add-five 5) (add-five 20)))

          10 25


          It returns a function that expects the rest of the arguments. This is useful when you have some data right now, and want to call the function with some other data later.



          Why would I make this change? Why should filter-items-by-term care about how it's being used? Why should it need to know that the caller needs to supply some of the data later? It shouldn't.




          Speaking of long lines, I'd break up the get-time body:



          (defn get-time [date-string]
          (.format (java.text.SimpleDateFormat. "dd/MM/yyyy - HH:mm")
          (new java.util.Date date-string)))


          Align everything properly based on indentation (like you would in Python), and Par-infer (Parenthesis Inference) can automatically handle closing braces for you. I never manually add )s when writing Clojure. IntelliJ's Cursive plugin (both free) includes Par-Infer which infers where they should be and adds them for me. I highly recommend this set-up if you plan on writing Clojure.




          get-selected-option is redundant and has a confusing name. It isn't actually "getting" anything, it's a predicate. It also doesn't make sense to write (if pred? true false). The predicate already returns true or false (or at least a truthy/falsey value), so the if here isn't needed. I'd change it to:



          (defn is-filter-term? [value filterTerm]
          (= value filterTerm))


          Although it could be argued that this function is so simple that it should just be inlined. It's pretty clear what a simple call to = is checking.




          For render-authors and the function below it, I'd just use map here. Especially since you don't need any filtering or use of :let, map would likely be cleaner:



          (defn render-authors [content filterTerm]
          (map #(render-author-options (first %) filterTerm)
          content))


          But again, this a matter of taste. I will say though, if you have a iterable collection like content, it's a good idea to match Clojure convention and have it as the last parameter of the function. That way, you can do something like:



          (->> content
          (map some-transformation)
          (render-authors some-filter-term) ; Wouldn't be possible with your current order
          (filter some-predicate))


          Threading macros (-> and ->> mainly) are pretty common, and writing functions that work well with them will make your life easier. You didn't use and threading macros in your code, but I also don't see any good opportunities to use them either. I'd practice using them if you intend to keep writing Clojure, as they're exceedingly helpful.




          In get-highlighted-text-by-key, there's a few notable things.



          One, I'm not sure title is necessary here. It's needed in two places, and neither of those places seem to even need it. Unless my brain is just fried, (conj title (apply list nested-title)) is just conjoining (apply list nested-title) to an empty vector, which will just result in a single element vector. It would make more sense to just write:



          (vector (apply list nested-title))


          I also don't understand why you then wrap that in a (apply list, which just converts it to a list. You convert nested-title to a list with the first (apply list, then wrap it in a vector with (conj [], then convert the vector to a list with a second (apply list I'm not sure what you're trying to do here. It looks like that entire line could be reduced to just:



          (list (apply list nested-title))


          Although, I'll note that that plain lists aren't used very often unless you're writing macros. Lists are simple linked lists, so they can't do O(1) lookups by index. Use vectors in the vast majority of cases unless you have good reason to use something else (like you're writing a macro). Simply returning the lazy list that for evaluates to would likely be fine here. The caller can force the result if they want to.



          Second,



          (let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
          (if match


          (Besides being way too long of a line) is a perfect case to make use of if-let:



          (if-let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
          (... If truthy, do this, using "match" ...)
          (... If falsey, do this, without having access to "match")


          if-let checks if the bound variable is truthy. If it is, it executes the first body with the truthy binding in scope. If it's falsey, it executes the second body, without the binding in scope.





          I think that's all the major things I can find. Good luck. Hopefully this was helpful.






          share|improve this answer











          $endgroup$













            Your Answer





            StackExchange.ifUsing("editor", function ()
            return StackExchange.using("mathjaxEditing", function ()
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            );
            );
            , "mathjax-editing");

            StackExchange.ifUsing("editor", function ()
            StackExchange.using("externalEditor", function ()
            StackExchange.using("snippets", function ()
            StackExchange.snippets.init();
            );
            );
            , "code-snippets");

            StackExchange.ready(function()
            var channelOptions =
            tags: "".split(" "),
            id: "196"
            ;
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function()
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled)
            StackExchange.using("snippets", function()
            createEditor();
            );

            else
            createEditor();

            );

            function createEditor()
            StackExchange.prepareEditor(
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: false,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            imageUploader:
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            ,
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            );



            );













            draft saved

            draft discarded


















            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f214726%2frss-feed-viewer-in-clojure%23new-answer', 'question_page');

            );

            Post as a guest















            Required, but never shown

























            2 Answers
            2






            active

            oldest

            votes








            2 Answers
            2






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            0












            $begingroup$

            Thanks a lot for the thorough explanation and analysis!



            I tried your suggestion for updating the 'get-highlighted-text-by-key' method, unfortunately, it didn't work right away though. I managed to make it work like so:



            (defn get-highlighted-text-by-key [item filterTerm key]
            (if-let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))
            (apply str (get item key)))]
            ;; drops the first element of the match vector, since that's the whole string
            (let [nested-title (for [substr (drop 1 match)]
            (if (= (simplify-string substr) (simplify-string filterTerm))
            [:span :class "highlight" substr]
            [:span substr]
            )
            )]
            nested-title)
            [:span (get item key "No title specified")]))


            This is also way simpler and makes more sense. When I wrote the method I had the feeling that something is off, but I couldn't quite wrap my head around it. So thanks for clearing that up!






            share|improve this answer











            $endgroup$












            • $begingroup$
              No problem. Glad to help. Note though, I'm not sure that this should be an answer. It might get flagged.
              $endgroup$
              – Carcigenicate
              Mar 5 at 20:42










            • $begingroup$
              Yes, I tried posting as a comment, but I didn't manage to make it work for the code though :/
              $endgroup$
              – Mario
              Mar 5 at 20:43
















            0












            $begingroup$

            Thanks a lot for the thorough explanation and analysis!



            I tried your suggestion for updating the 'get-highlighted-text-by-key' method, unfortunately, it didn't work right away though. I managed to make it work like so:



            (defn get-highlighted-text-by-key [item filterTerm key]
            (if-let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))
            (apply str (get item key)))]
            ;; drops the first element of the match vector, since that's the whole string
            (let [nested-title (for [substr (drop 1 match)]
            (if (= (simplify-string substr) (simplify-string filterTerm))
            [:span :class "highlight" substr]
            [:span substr]
            )
            )]
            nested-title)
            [:span (get item key "No title specified")]))


            This is also way simpler and makes more sense. When I wrote the method I had the feeling that something is off, but I couldn't quite wrap my head around it. So thanks for clearing that up!






            share|improve this answer











            $endgroup$












            • $begingroup$
              No problem. Glad to help. Note though, I'm not sure that this should be an answer. It might get flagged.
              $endgroup$
              – Carcigenicate
              Mar 5 at 20:42










            • $begingroup$
              Yes, I tried posting as a comment, but I didn't manage to make it work for the code though :/
              $endgroup$
              – Mario
              Mar 5 at 20:43














            0












            0








            0





            $begingroup$

            Thanks a lot for the thorough explanation and analysis!



            I tried your suggestion for updating the 'get-highlighted-text-by-key' method, unfortunately, it didn't work right away though. I managed to make it work like so:



            (defn get-highlighted-text-by-key [item filterTerm key]
            (if-let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))
            (apply str (get item key)))]
            ;; drops the first element of the match vector, since that's the whole string
            (let [nested-title (for [substr (drop 1 match)]
            (if (= (simplify-string substr) (simplify-string filterTerm))
            [:span :class "highlight" substr]
            [:span substr]
            )
            )]
            nested-title)
            [:span (get item key "No title specified")]))


            This is also way simpler and makes more sense. When I wrote the method I had the feeling that something is off, but I couldn't quite wrap my head around it. So thanks for clearing that up!






            share|improve this answer











            $endgroup$



            Thanks a lot for the thorough explanation and analysis!



            I tried your suggestion for updating the 'get-highlighted-text-by-key' method, unfortunately, it didn't work right away though. I managed to make it work like so:



            (defn get-highlighted-text-by-key [item filterTerm key]
            (if-let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))
            (apply str (get item key)))]
            ;; drops the first element of the match vector, since that's the whole string
            (let [nested-title (for [substr (drop 1 match)]
            (if (= (simplify-string substr) (simplify-string filterTerm))
            [:span :class "highlight" substr]
            [:span substr]
            )
            )]
            nested-title)
            [:span (get item key "No title specified")]))


            This is also way simpler and makes more sense. When I wrote the method I had the feeling that something is off, but I couldn't quite wrap my head around it. So thanks for clearing that up!







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Mar 5 at 20:41









            Carcigenicate

            3,84011632




            3,84011632










            answered Mar 5 at 20:35









            MarioMario

            111




            111











            • $begingroup$
              No problem. Glad to help. Note though, I'm not sure that this should be an answer. It might get flagged.
              $endgroup$
              – Carcigenicate
              Mar 5 at 20:42










            • $begingroup$
              Yes, I tried posting as a comment, but I didn't manage to make it work for the code though :/
              $endgroup$
              – Mario
              Mar 5 at 20:43

















            • $begingroup$
              No problem. Glad to help. Note though, I'm not sure that this should be an answer. It might get flagged.
              $endgroup$
              – Carcigenicate
              Mar 5 at 20:42










            • $begingroup$
              Yes, I tried posting as a comment, but I didn't manage to make it work for the code though :/
              $endgroup$
              – Mario
              Mar 5 at 20:43
















            $begingroup$
            No problem. Glad to help. Note though, I'm not sure that this should be an answer. It might get flagged.
            $endgroup$
            – Carcigenicate
            Mar 5 at 20:42




            $begingroup$
            No problem. Glad to help. Note though, I'm not sure that this should be an answer. It might get flagged.
            $endgroup$
            – Carcigenicate
            Mar 5 at 20:42












            $begingroup$
            Yes, I tried posting as a comment, but I didn't manage to make it work for the code though :/
            $endgroup$
            – Mario
            Mar 5 at 20:43





            $begingroup$
            Yes, I tried posting as a comment, but I didn't manage to make it work for the code though :/
            $endgroup$
            – Mario
            Mar 5 at 20:43














            0












            $begingroup$

            This code is quite good for someone learning the language. You haven't made any of the common errors such as using def inside of function instead of let. I don't see anything really outright wrong with the code, so I'll just be commenting on style and things that I think can be improved. I'll just go top to bottom after addressing some things you mentioned.





            . . . we're not quite sure if our code complies with functional programming paradigms.




            Yes, this code is functional. You aren't needlessly mutating anything by abusing atoms or relying on side-effects. Really, those are major deciding factors.




            (:use [rss-feeds-webapp.helper :refer :all])


            Shouldn't be there. That should be tucked inside the :require with everything else. Use of :use is discouraged, as is :refer :all in many cases. Blanket unqualified imports of namespaces aren't ideal. Say you come back to this project a year from now. Are you going to remember what functions come from what modules? Unless the names provide enough context, you may have trouble. Bulk unqualified imports also increase the chance of having a name conflict. Always try to use :as, or :refer [...] when importing. That way you can easily see what code is coming from where, and avoid polluting your namespace.




            (.getBytes (:body (client/get link)) "UTF-8")


            That would benefit from using a type hint.



            ; I'm assuming :body is returning a String
            (ByteArrayInputStream. ^String (.getBytes (:body (client/get link)) "UTF-8"))


            Not only does that help a smart IDE like IntelliJ give you better autocomplete suggestions, it also makes your code faster by avoiding reflection. If you run lein check, your call to .getBytes will cause the following warning:



            Reflection warning, your-file-here.clj - reference to field getBytes can't be resolved.


            Avoiding reflection isn't a big deal in this case, but it's a good thing to keep in mind.




            get-content and similar functions make good use of for. I'll just point out though that they can also be written in terms of map and filter as well:



            (defn get-content [item tag]
            (->> item
            (filter #(= tag (:tag %)))
            (map #(first (:content %)))))

            ; Or

            (defn get-content [item tag]
            (->> item
            (filter #(= tag (:tag %)))
            (map (comp first :content))))


            If you already have a ->> "pipe" going, it may prove to be cleaner to use map/filter here instead of for. This is purely a matter of taste though.




            (defn filter-items-by-term [term author]
            (let [term (or term "")]
            (let [author (or author "")]


            contains needless nesting. It can simply be



            (defn filter-items-by-term [term author]
            (let [term (or term "")
            author (or author "")]



            A lot of your code (like in the example immediately above), you're doing something like



            (or some-parameter a-default-value)


            Now, this is a good way of dealing with possibly-nil values. The placement of the check is weird though. Take a look at filter-items-by-term. Why are term and author possibly nil? Because get-feed-data takes optional parameters, and passes the data, unchecked, to filter-items-by-term. This means that part of the implementation of filter-items-by-term (checking for nil values) is needed due to the implementation of get-feed-data (passing potentially nil values). What if you change how one of the functions work and forget to change the other? It also seems needlessly complicated that many of your functions are trying to "protect themselves" by assuming that bad data may be handed in. It would be much cleaner to expect that the caller, when possible, checks the data prior to calling. All your functions should expect valid data. If the caller has bad data, it's up to them to fix it. I'd make the following changes:



            (defn filter-items-by-term [term author]
            ; Assumes valid data is being passed!
            (fn [[item]]

            (defn get-feed-data [link & [filterTerm authorTerm]]
            ; It's now this function's duty to verify its own data
            (filter (filter-items-by-term (or filterTerm "")
            (or authorTerm ""))
            (get-items [] link)))


            And similarly for the other cases like this.



            I also cleaned up get-feed-data. It had two things that I didn't like:



            • Trailing ) on a new line, like you would have a } in Java. This isn't necessary if you're using a good IDE and consistent indentation. It seems like one of you is using this style while the other isn't, since it's inconsistent. Even worse than using an unidiomatic style is applying the style inconsistently. Your teacher will mark you down for inconsistency alone if they're paying attention.


            • Trying to shove a bunch on one line. It's much better to break up long lines for readability.



            Also in that same function, you have the long anonymous function being returned. In it, you repeatedly access item using keys. It might be cleaner to deconstruct item in the parameter list:



            (fn [[:keys [title description creator]]]
            (and (or
            (string/includes?
            (simplify-string (apply str title))
            (simplify-string term))
            (string/includes?
            (simplify-string (apply str description))
            (simplify-string term)))

            (string/includes?
            (simplify-string (apply str creator))
            (simplify-string author))))


            Also note, (get item :title) can be written simply as (:title item). Keywords implement IFn:



            (ifn? :hello)
            => true


            They are usable as functions that return the corresponding element when applied to a map.




            get-feed-data could also make use of partial. You're having the function return another function so you can supply some data ahead of time, then have filter call it with the last bit of data. This is very common, and is the situation that partial was made for. Id make the following changes:



            ; Note how "item" was simply added as a third parameter
            (defn filter-items-by-term [term author :keys [title description creator]]
            ; And now a function isn't returned
            (and (or
            (string/includes?
            (simplify-string (apply str title))
            (simplify-string term))
            (string/includes?
            (simplify-string (apply str description))
            (simplify-string term)))

            (string/includes?
            (simplify-string (apply str creator))
            (simplify-string author))))


            And now:



            (defn get-feed-data [link & [filterTerm authorTerm]]
            ; Partially apply "filterTerm" and "autherTerm" to "filter-items-by-term"
            (filter (partial filter-items-by-term filterTerm authorTerm)
            (get-items [] link)))


            or, just manually wrap it in another function (#() here):



            (defn get-feed-data [link & [filterTerm authorTerm]]
            (filter #(filter-items-by-term filterTerm authorTerm %)
            (get-items [] link)))


            partial may seem complicated at first, but this example should make it clearer:



            (let [add-five (partial + 5)]
            (println (add-five 5) (add-five 20)))

            10 25


            It returns a function that expects the rest of the arguments. This is useful when you have some data right now, and want to call the function with some other data later.



            Why would I make this change? Why should filter-items-by-term care about how it's being used? Why should it need to know that the caller needs to supply some of the data later? It shouldn't.




            Speaking of long lines, I'd break up the get-time body:



            (defn get-time [date-string]
            (.format (java.text.SimpleDateFormat. "dd/MM/yyyy - HH:mm")
            (new java.util.Date date-string)))


            Align everything properly based on indentation (like you would in Python), and Par-infer (Parenthesis Inference) can automatically handle closing braces for you. I never manually add )s when writing Clojure. IntelliJ's Cursive plugin (both free) includes Par-Infer which infers where they should be and adds them for me. I highly recommend this set-up if you plan on writing Clojure.




            get-selected-option is redundant and has a confusing name. It isn't actually "getting" anything, it's a predicate. It also doesn't make sense to write (if pred? true false). The predicate already returns true or false (or at least a truthy/falsey value), so the if here isn't needed. I'd change it to:



            (defn is-filter-term? [value filterTerm]
            (= value filterTerm))


            Although it could be argued that this function is so simple that it should just be inlined. It's pretty clear what a simple call to = is checking.




            For render-authors and the function below it, I'd just use map here. Especially since you don't need any filtering or use of :let, map would likely be cleaner:



            (defn render-authors [content filterTerm]
            (map #(render-author-options (first %) filterTerm)
            content))


            But again, this a matter of taste. I will say though, if you have a iterable collection like content, it's a good idea to match Clojure convention and have it as the last parameter of the function. That way, you can do something like:



            (->> content
            (map some-transformation)
            (render-authors some-filter-term) ; Wouldn't be possible with your current order
            (filter some-predicate))


            Threading macros (-> and ->> mainly) are pretty common, and writing functions that work well with them will make your life easier. You didn't use and threading macros in your code, but I also don't see any good opportunities to use them either. I'd practice using them if you intend to keep writing Clojure, as they're exceedingly helpful.




            In get-highlighted-text-by-key, there's a few notable things.



            One, I'm not sure title is necessary here. It's needed in two places, and neither of those places seem to even need it. Unless my brain is just fried, (conj title (apply list nested-title)) is just conjoining (apply list nested-title) to an empty vector, which will just result in a single element vector. It would make more sense to just write:



            (vector (apply list nested-title))


            I also don't understand why you then wrap that in a (apply list, which just converts it to a list. You convert nested-title to a list with the first (apply list, then wrap it in a vector with (conj [], then convert the vector to a list with a second (apply list I'm not sure what you're trying to do here. It looks like that entire line could be reduced to just:



            (list (apply list nested-title))


            Although, I'll note that that plain lists aren't used very often unless you're writing macros. Lists are simple linked lists, so they can't do O(1) lookups by index. Use vectors in the vast majority of cases unless you have good reason to use something else (like you're writing a macro). Simply returning the lazy list that for evaluates to would likely be fine here. The caller can force the result if they want to.



            Second,



            (let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
            (if match


            (Besides being way too long of a line) is a perfect case to make use of if-let:



            (if-let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
            (... If truthy, do this, using "match" ...)
            (... If falsey, do this, without having access to "match")


            if-let checks if the bound variable is truthy. If it is, it executes the first body with the truthy binding in scope. If it's falsey, it executes the second body, without the binding in scope.





            I think that's all the major things I can find. Good luck. Hopefully this was helpful.






            share|improve this answer











            $endgroup$

















              0












              $begingroup$

              This code is quite good for someone learning the language. You haven't made any of the common errors such as using def inside of function instead of let. I don't see anything really outright wrong with the code, so I'll just be commenting on style and things that I think can be improved. I'll just go top to bottom after addressing some things you mentioned.





              . . . we're not quite sure if our code complies with functional programming paradigms.




              Yes, this code is functional. You aren't needlessly mutating anything by abusing atoms or relying on side-effects. Really, those are major deciding factors.




              (:use [rss-feeds-webapp.helper :refer :all])


              Shouldn't be there. That should be tucked inside the :require with everything else. Use of :use is discouraged, as is :refer :all in many cases. Blanket unqualified imports of namespaces aren't ideal. Say you come back to this project a year from now. Are you going to remember what functions come from what modules? Unless the names provide enough context, you may have trouble. Bulk unqualified imports also increase the chance of having a name conflict. Always try to use :as, or :refer [...] when importing. That way you can easily see what code is coming from where, and avoid polluting your namespace.




              (.getBytes (:body (client/get link)) "UTF-8")


              That would benefit from using a type hint.



              ; I'm assuming :body is returning a String
              (ByteArrayInputStream. ^String (.getBytes (:body (client/get link)) "UTF-8"))


              Not only does that help a smart IDE like IntelliJ give you better autocomplete suggestions, it also makes your code faster by avoiding reflection. If you run lein check, your call to .getBytes will cause the following warning:



              Reflection warning, your-file-here.clj - reference to field getBytes can't be resolved.


              Avoiding reflection isn't a big deal in this case, but it's a good thing to keep in mind.




              get-content and similar functions make good use of for. I'll just point out though that they can also be written in terms of map and filter as well:



              (defn get-content [item tag]
              (->> item
              (filter #(= tag (:tag %)))
              (map #(first (:content %)))))

              ; Or

              (defn get-content [item tag]
              (->> item
              (filter #(= tag (:tag %)))
              (map (comp first :content))))


              If you already have a ->> "pipe" going, it may prove to be cleaner to use map/filter here instead of for. This is purely a matter of taste though.




              (defn filter-items-by-term [term author]
              (let [term (or term "")]
              (let [author (or author "")]


              contains needless nesting. It can simply be



              (defn filter-items-by-term [term author]
              (let [term (or term "")
              author (or author "")]



              A lot of your code (like in the example immediately above), you're doing something like



              (or some-parameter a-default-value)


              Now, this is a good way of dealing with possibly-nil values. The placement of the check is weird though. Take a look at filter-items-by-term. Why are term and author possibly nil? Because get-feed-data takes optional parameters, and passes the data, unchecked, to filter-items-by-term. This means that part of the implementation of filter-items-by-term (checking for nil values) is needed due to the implementation of get-feed-data (passing potentially nil values). What if you change how one of the functions work and forget to change the other? It also seems needlessly complicated that many of your functions are trying to "protect themselves" by assuming that bad data may be handed in. It would be much cleaner to expect that the caller, when possible, checks the data prior to calling. All your functions should expect valid data. If the caller has bad data, it's up to them to fix it. I'd make the following changes:



              (defn filter-items-by-term [term author]
              ; Assumes valid data is being passed!
              (fn [[item]]

              (defn get-feed-data [link & [filterTerm authorTerm]]
              ; It's now this function's duty to verify its own data
              (filter (filter-items-by-term (or filterTerm "")
              (or authorTerm ""))
              (get-items [] link)))


              And similarly for the other cases like this.



              I also cleaned up get-feed-data. It had two things that I didn't like:



              • Trailing ) on a new line, like you would have a } in Java. This isn't necessary if you're using a good IDE and consistent indentation. It seems like one of you is using this style while the other isn't, since it's inconsistent. Even worse than using an unidiomatic style is applying the style inconsistently. Your teacher will mark you down for inconsistency alone if they're paying attention.


              • Trying to shove a bunch on one line. It's much better to break up long lines for readability.



              Also in that same function, you have the long anonymous function being returned. In it, you repeatedly access item using keys. It might be cleaner to deconstruct item in the parameter list:



              (fn [[:keys [title description creator]]]
              (and (or
              (string/includes?
              (simplify-string (apply str title))
              (simplify-string term))
              (string/includes?
              (simplify-string (apply str description))
              (simplify-string term)))

              (string/includes?
              (simplify-string (apply str creator))
              (simplify-string author))))


              Also note, (get item :title) can be written simply as (:title item). Keywords implement IFn:



              (ifn? :hello)
              => true


              They are usable as functions that return the corresponding element when applied to a map.




              get-feed-data could also make use of partial. You're having the function return another function so you can supply some data ahead of time, then have filter call it with the last bit of data. This is very common, and is the situation that partial was made for. Id make the following changes:



              ; Note how "item" was simply added as a third parameter
              (defn filter-items-by-term [term author :keys [title description creator]]
              ; And now a function isn't returned
              (and (or
              (string/includes?
              (simplify-string (apply str title))
              (simplify-string term))
              (string/includes?
              (simplify-string (apply str description))
              (simplify-string term)))

              (string/includes?
              (simplify-string (apply str creator))
              (simplify-string author))))


              And now:



              (defn get-feed-data [link & [filterTerm authorTerm]]
              ; Partially apply "filterTerm" and "autherTerm" to "filter-items-by-term"
              (filter (partial filter-items-by-term filterTerm authorTerm)
              (get-items [] link)))


              or, just manually wrap it in another function (#() here):



              (defn get-feed-data [link & [filterTerm authorTerm]]
              (filter #(filter-items-by-term filterTerm authorTerm %)
              (get-items [] link)))


              partial may seem complicated at first, but this example should make it clearer:



              (let [add-five (partial + 5)]
              (println (add-five 5) (add-five 20)))

              10 25


              It returns a function that expects the rest of the arguments. This is useful when you have some data right now, and want to call the function with some other data later.



              Why would I make this change? Why should filter-items-by-term care about how it's being used? Why should it need to know that the caller needs to supply some of the data later? It shouldn't.




              Speaking of long lines, I'd break up the get-time body:



              (defn get-time [date-string]
              (.format (java.text.SimpleDateFormat. "dd/MM/yyyy - HH:mm")
              (new java.util.Date date-string)))


              Align everything properly based on indentation (like you would in Python), and Par-infer (Parenthesis Inference) can automatically handle closing braces for you. I never manually add )s when writing Clojure. IntelliJ's Cursive plugin (both free) includes Par-Infer which infers where they should be and adds them for me. I highly recommend this set-up if you plan on writing Clojure.




              get-selected-option is redundant and has a confusing name. It isn't actually "getting" anything, it's a predicate. It also doesn't make sense to write (if pred? true false). The predicate already returns true or false (or at least a truthy/falsey value), so the if here isn't needed. I'd change it to:



              (defn is-filter-term? [value filterTerm]
              (= value filterTerm))


              Although it could be argued that this function is so simple that it should just be inlined. It's pretty clear what a simple call to = is checking.




              For render-authors and the function below it, I'd just use map here. Especially since you don't need any filtering or use of :let, map would likely be cleaner:



              (defn render-authors [content filterTerm]
              (map #(render-author-options (first %) filterTerm)
              content))


              But again, this a matter of taste. I will say though, if you have a iterable collection like content, it's a good idea to match Clojure convention and have it as the last parameter of the function. That way, you can do something like:



              (->> content
              (map some-transformation)
              (render-authors some-filter-term) ; Wouldn't be possible with your current order
              (filter some-predicate))


              Threading macros (-> and ->> mainly) are pretty common, and writing functions that work well with them will make your life easier. You didn't use and threading macros in your code, but I also don't see any good opportunities to use them either. I'd practice using them if you intend to keep writing Clojure, as they're exceedingly helpful.




              In get-highlighted-text-by-key, there's a few notable things.



              One, I'm not sure title is necessary here. It's needed in two places, and neither of those places seem to even need it. Unless my brain is just fried, (conj title (apply list nested-title)) is just conjoining (apply list nested-title) to an empty vector, which will just result in a single element vector. It would make more sense to just write:



              (vector (apply list nested-title))


              I also don't understand why you then wrap that in a (apply list, which just converts it to a list. You convert nested-title to a list with the first (apply list, then wrap it in a vector with (conj [], then convert the vector to a list with a second (apply list I'm not sure what you're trying to do here. It looks like that entire line could be reduced to just:



              (list (apply list nested-title))


              Although, I'll note that that plain lists aren't used very often unless you're writing macros. Lists are simple linked lists, so they can't do O(1) lookups by index. Use vectors in the vast majority of cases unless you have good reason to use something else (like you're writing a macro). Simply returning the lazy list that for evaluates to would likely be fine here. The caller can force the result if they want to.



              Second,



              (let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
              (if match


              (Besides being way too long of a line) is a perfect case to make use of if-let:



              (if-let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
              (... If truthy, do this, using "match" ...)
              (... If falsey, do this, without having access to "match")


              if-let checks if the bound variable is truthy. If it is, it executes the first body with the truthy binding in scope. If it's falsey, it executes the second body, without the binding in scope.





              I think that's all the major things I can find. Good luck. Hopefully this was helpful.






              share|improve this answer











              $endgroup$















                0












                0








                0





                $begingroup$

                This code is quite good for someone learning the language. You haven't made any of the common errors such as using def inside of function instead of let. I don't see anything really outright wrong with the code, so I'll just be commenting on style and things that I think can be improved. I'll just go top to bottom after addressing some things you mentioned.





                . . . we're not quite sure if our code complies with functional programming paradigms.




                Yes, this code is functional. You aren't needlessly mutating anything by abusing atoms or relying on side-effects. Really, those are major deciding factors.




                (:use [rss-feeds-webapp.helper :refer :all])


                Shouldn't be there. That should be tucked inside the :require with everything else. Use of :use is discouraged, as is :refer :all in many cases. Blanket unqualified imports of namespaces aren't ideal. Say you come back to this project a year from now. Are you going to remember what functions come from what modules? Unless the names provide enough context, you may have trouble. Bulk unqualified imports also increase the chance of having a name conflict. Always try to use :as, or :refer [...] when importing. That way you can easily see what code is coming from where, and avoid polluting your namespace.




                (.getBytes (:body (client/get link)) "UTF-8")


                That would benefit from using a type hint.



                ; I'm assuming :body is returning a String
                (ByteArrayInputStream. ^String (.getBytes (:body (client/get link)) "UTF-8"))


                Not only does that help a smart IDE like IntelliJ give you better autocomplete suggestions, it also makes your code faster by avoiding reflection. If you run lein check, your call to .getBytes will cause the following warning:



                Reflection warning, your-file-here.clj - reference to field getBytes can't be resolved.


                Avoiding reflection isn't a big deal in this case, but it's a good thing to keep in mind.




                get-content and similar functions make good use of for. I'll just point out though that they can also be written in terms of map and filter as well:



                (defn get-content [item tag]
                (->> item
                (filter #(= tag (:tag %)))
                (map #(first (:content %)))))

                ; Or

                (defn get-content [item tag]
                (->> item
                (filter #(= tag (:tag %)))
                (map (comp first :content))))


                If you already have a ->> "pipe" going, it may prove to be cleaner to use map/filter here instead of for. This is purely a matter of taste though.




                (defn filter-items-by-term [term author]
                (let [term (or term "")]
                (let [author (or author "")]


                contains needless nesting. It can simply be



                (defn filter-items-by-term [term author]
                (let [term (or term "")
                author (or author "")]



                A lot of your code (like in the example immediately above), you're doing something like



                (or some-parameter a-default-value)


                Now, this is a good way of dealing with possibly-nil values. The placement of the check is weird though. Take a look at filter-items-by-term. Why are term and author possibly nil? Because get-feed-data takes optional parameters, and passes the data, unchecked, to filter-items-by-term. This means that part of the implementation of filter-items-by-term (checking for nil values) is needed due to the implementation of get-feed-data (passing potentially nil values). What if you change how one of the functions work and forget to change the other? It also seems needlessly complicated that many of your functions are trying to "protect themselves" by assuming that bad data may be handed in. It would be much cleaner to expect that the caller, when possible, checks the data prior to calling. All your functions should expect valid data. If the caller has bad data, it's up to them to fix it. I'd make the following changes:



                (defn filter-items-by-term [term author]
                ; Assumes valid data is being passed!
                (fn [[item]]

                (defn get-feed-data [link & [filterTerm authorTerm]]
                ; It's now this function's duty to verify its own data
                (filter (filter-items-by-term (or filterTerm "")
                (or authorTerm ""))
                (get-items [] link)))


                And similarly for the other cases like this.



                I also cleaned up get-feed-data. It had two things that I didn't like:



                • Trailing ) on a new line, like you would have a } in Java. This isn't necessary if you're using a good IDE and consistent indentation. It seems like one of you is using this style while the other isn't, since it's inconsistent. Even worse than using an unidiomatic style is applying the style inconsistently. Your teacher will mark you down for inconsistency alone if they're paying attention.


                • Trying to shove a bunch on one line. It's much better to break up long lines for readability.



                Also in that same function, you have the long anonymous function being returned. In it, you repeatedly access item using keys. It might be cleaner to deconstruct item in the parameter list:



                (fn [[:keys [title description creator]]]
                (and (or
                (string/includes?
                (simplify-string (apply str title))
                (simplify-string term))
                (string/includes?
                (simplify-string (apply str description))
                (simplify-string term)))

                (string/includes?
                (simplify-string (apply str creator))
                (simplify-string author))))


                Also note, (get item :title) can be written simply as (:title item). Keywords implement IFn:



                (ifn? :hello)
                => true


                They are usable as functions that return the corresponding element when applied to a map.




                get-feed-data could also make use of partial. You're having the function return another function so you can supply some data ahead of time, then have filter call it with the last bit of data. This is very common, and is the situation that partial was made for. Id make the following changes:



                ; Note how "item" was simply added as a third parameter
                (defn filter-items-by-term [term author :keys [title description creator]]
                ; And now a function isn't returned
                (and (or
                (string/includes?
                (simplify-string (apply str title))
                (simplify-string term))
                (string/includes?
                (simplify-string (apply str description))
                (simplify-string term)))

                (string/includes?
                (simplify-string (apply str creator))
                (simplify-string author))))


                And now:



                (defn get-feed-data [link & [filterTerm authorTerm]]
                ; Partially apply "filterTerm" and "autherTerm" to "filter-items-by-term"
                (filter (partial filter-items-by-term filterTerm authorTerm)
                (get-items [] link)))


                or, just manually wrap it in another function (#() here):



                (defn get-feed-data [link & [filterTerm authorTerm]]
                (filter #(filter-items-by-term filterTerm authorTerm %)
                (get-items [] link)))


                partial may seem complicated at first, but this example should make it clearer:



                (let [add-five (partial + 5)]
                (println (add-five 5) (add-five 20)))

                10 25


                It returns a function that expects the rest of the arguments. This is useful when you have some data right now, and want to call the function with some other data later.



                Why would I make this change? Why should filter-items-by-term care about how it's being used? Why should it need to know that the caller needs to supply some of the data later? It shouldn't.




                Speaking of long lines, I'd break up the get-time body:



                (defn get-time [date-string]
                (.format (java.text.SimpleDateFormat. "dd/MM/yyyy - HH:mm")
                (new java.util.Date date-string)))


                Align everything properly based on indentation (like you would in Python), and Par-infer (Parenthesis Inference) can automatically handle closing braces for you. I never manually add )s when writing Clojure. IntelliJ's Cursive plugin (both free) includes Par-Infer which infers where they should be and adds them for me. I highly recommend this set-up if you plan on writing Clojure.




                get-selected-option is redundant and has a confusing name. It isn't actually "getting" anything, it's a predicate. It also doesn't make sense to write (if pred? true false). The predicate already returns true or false (or at least a truthy/falsey value), so the if here isn't needed. I'd change it to:



                (defn is-filter-term? [value filterTerm]
                (= value filterTerm))


                Although it could be argued that this function is so simple that it should just be inlined. It's pretty clear what a simple call to = is checking.




                For render-authors and the function below it, I'd just use map here. Especially since you don't need any filtering or use of :let, map would likely be cleaner:



                (defn render-authors [content filterTerm]
                (map #(render-author-options (first %) filterTerm)
                content))


                But again, this a matter of taste. I will say though, if you have a iterable collection like content, it's a good idea to match Clojure convention and have it as the last parameter of the function. That way, you can do something like:



                (->> content
                (map some-transformation)
                (render-authors some-filter-term) ; Wouldn't be possible with your current order
                (filter some-predicate))


                Threading macros (-> and ->> mainly) are pretty common, and writing functions that work well with them will make your life easier. You didn't use and threading macros in your code, but I also don't see any good opportunities to use them either. I'd practice using them if you intend to keep writing Clojure, as they're exceedingly helpful.




                In get-highlighted-text-by-key, there's a few notable things.



                One, I'm not sure title is necessary here. It's needed in two places, and neither of those places seem to even need it. Unless my brain is just fried, (conj title (apply list nested-title)) is just conjoining (apply list nested-title) to an empty vector, which will just result in a single element vector. It would make more sense to just write:



                (vector (apply list nested-title))


                I also don't understand why you then wrap that in a (apply list, which just converts it to a list. You convert nested-title to a list with the first (apply list, then wrap it in a vector with (conj [], then convert the vector to a list with a second (apply list I'm not sure what you're trying to do here. It looks like that entire line could be reduced to just:



                (list (apply list nested-title))


                Although, I'll note that that plain lists aren't used very often unless you're writing macros. Lists are simple linked lists, so they can't do O(1) lookups by index. Use vectors in the vast majority of cases unless you have good reason to use something else (like you're writing a macro). Simply returning the lazy list that for evaluates to would likely be fine here. The caller can force the result if they want to.



                Second,



                (let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
                (if match


                (Besides being way too long of a line) is a perfect case to make use of if-let:



                (if-let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
                (... If truthy, do this, using "match" ...)
                (... If falsey, do this, without having access to "match")


                if-let checks if the bound variable is truthy. If it is, it executes the first body with the truthy binding in scope. If it's falsey, it executes the second body, without the binding in scope.





                I think that's all the major things I can find. Good luck. Hopefully this was helpful.






                share|improve this answer











                $endgroup$



                This code is quite good for someone learning the language. You haven't made any of the common errors such as using def inside of function instead of let. I don't see anything really outright wrong with the code, so I'll just be commenting on style and things that I think can be improved. I'll just go top to bottom after addressing some things you mentioned.





                . . . we're not quite sure if our code complies with functional programming paradigms.




                Yes, this code is functional. You aren't needlessly mutating anything by abusing atoms or relying on side-effects. Really, those are major deciding factors.




                (:use [rss-feeds-webapp.helper :refer :all])


                Shouldn't be there. That should be tucked inside the :require with everything else. Use of :use is discouraged, as is :refer :all in many cases. Blanket unqualified imports of namespaces aren't ideal. Say you come back to this project a year from now. Are you going to remember what functions come from what modules? Unless the names provide enough context, you may have trouble. Bulk unqualified imports also increase the chance of having a name conflict. Always try to use :as, or :refer [...] when importing. That way you can easily see what code is coming from where, and avoid polluting your namespace.




                (.getBytes (:body (client/get link)) "UTF-8")


                That would benefit from using a type hint.



                ; I'm assuming :body is returning a String
                (ByteArrayInputStream. ^String (.getBytes (:body (client/get link)) "UTF-8"))


                Not only does that help a smart IDE like IntelliJ give you better autocomplete suggestions, it also makes your code faster by avoiding reflection. If you run lein check, your call to .getBytes will cause the following warning:



                Reflection warning, your-file-here.clj - reference to field getBytes can't be resolved.


                Avoiding reflection isn't a big deal in this case, but it's a good thing to keep in mind.




                get-content and similar functions make good use of for. I'll just point out though that they can also be written in terms of map and filter as well:



                (defn get-content [item tag]
                (->> item
                (filter #(= tag (:tag %)))
                (map #(first (:content %)))))

                ; Or

                (defn get-content [item tag]
                (->> item
                (filter #(= tag (:tag %)))
                (map (comp first :content))))


                If you already have a ->> "pipe" going, it may prove to be cleaner to use map/filter here instead of for. This is purely a matter of taste though.




                (defn filter-items-by-term [term author]
                (let [term (or term "")]
                (let [author (or author "")]


                contains needless nesting. It can simply be



                (defn filter-items-by-term [term author]
                (let [term (or term "")
                author (or author "")]



                A lot of your code (like in the example immediately above), you're doing something like



                (or some-parameter a-default-value)


                Now, this is a good way of dealing with possibly-nil values. The placement of the check is weird though. Take a look at filter-items-by-term. Why are term and author possibly nil? Because get-feed-data takes optional parameters, and passes the data, unchecked, to filter-items-by-term. This means that part of the implementation of filter-items-by-term (checking for nil values) is needed due to the implementation of get-feed-data (passing potentially nil values). What if you change how one of the functions work and forget to change the other? It also seems needlessly complicated that many of your functions are trying to "protect themselves" by assuming that bad data may be handed in. It would be much cleaner to expect that the caller, when possible, checks the data prior to calling. All your functions should expect valid data. If the caller has bad data, it's up to them to fix it. I'd make the following changes:



                (defn filter-items-by-term [term author]
                ; Assumes valid data is being passed!
                (fn [[item]]

                (defn get-feed-data [link & [filterTerm authorTerm]]
                ; It's now this function's duty to verify its own data
                (filter (filter-items-by-term (or filterTerm "")
                (or authorTerm ""))
                (get-items [] link)))


                And similarly for the other cases like this.



                I also cleaned up get-feed-data. It had two things that I didn't like:



                • Trailing ) on a new line, like you would have a } in Java. This isn't necessary if you're using a good IDE and consistent indentation. It seems like one of you is using this style while the other isn't, since it's inconsistent. Even worse than using an unidiomatic style is applying the style inconsistently. Your teacher will mark you down for inconsistency alone if they're paying attention.


                • Trying to shove a bunch on one line. It's much better to break up long lines for readability.



                Also in that same function, you have the long anonymous function being returned. In it, you repeatedly access item using keys. It might be cleaner to deconstruct item in the parameter list:



                (fn [[:keys [title description creator]]]
                (and (or
                (string/includes?
                (simplify-string (apply str title))
                (simplify-string term))
                (string/includes?
                (simplify-string (apply str description))
                (simplify-string term)))

                (string/includes?
                (simplify-string (apply str creator))
                (simplify-string author))))


                Also note, (get item :title) can be written simply as (:title item). Keywords implement IFn:



                (ifn? :hello)
                => true


                They are usable as functions that return the corresponding element when applied to a map.




                get-feed-data could also make use of partial. You're having the function return another function so you can supply some data ahead of time, then have filter call it with the last bit of data. This is very common, and is the situation that partial was made for. Id make the following changes:



                ; Note how "item" was simply added as a third parameter
                (defn filter-items-by-term [term author :keys [title description creator]]
                ; And now a function isn't returned
                (and (or
                (string/includes?
                (simplify-string (apply str title))
                (simplify-string term))
                (string/includes?
                (simplify-string (apply str description))
                (simplify-string term)))

                (string/includes?
                (simplify-string (apply str creator))
                (simplify-string author))))


                And now:



                (defn get-feed-data [link & [filterTerm authorTerm]]
                ; Partially apply "filterTerm" and "autherTerm" to "filter-items-by-term"
                (filter (partial filter-items-by-term filterTerm authorTerm)
                (get-items [] link)))


                or, just manually wrap it in another function (#() here):



                (defn get-feed-data [link & [filterTerm authorTerm]]
                (filter #(filter-items-by-term filterTerm authorTerm %)
                (get-items [] link)))


                partial may seem complicated at first, but this example should make it clearer:



                (let [add-five (partial + 5)]
                (println (add-five 5) (add-five 20)))

                10 25


                It returns a function that expects the rest of the arguments. This is useful when you have some data right now, and want to call the function with some other data later.



                Why would I make this change? Why should filter-items-by-term care about how it's being used? Why should it need to know that the caller needs to supply some of the data later? It shouldn't.




                Speaking of long lines, I'd break up the get-time body:



                (defn get-time [date-string]
                (.format (java.text.SimpleDateFormat. "dd/MM/yyyy - HH:mm")
                (new java.util.Date date-string)))


                Align everything properly based on indentation (like you would in Python), and Par-infer (Parenthesis Inference) can automatically handle closing braces for you. I never manually add )s when writing Clojure. IntelliJ's Cursive plugin (both free) includes Par-Infer which infers where they should be and adds them for me. I highly recommend this set-up if you plan on writing Clojure.




                get-selected-option is redundant and has a confusing name. It isn't actually "getting" anything, it's a predicate. It also doesn't make sense to write (if pred? true false). The predicate already returns true or false (or at least a truthy/falsey value), so the if here isn't needed. I'd change it to:



                (defn is-filter-term? [value filterTerm]
                (= value filterTerm))


                Although it could be argued that this function is so simple that it should just be inlined. It's pretty clear what a simple call to = is checking.




                For render-authors and the function below it, I'd just use map here. Especially since you don't need any filtering or use of :let, map would likely be cleaner:



                (defn render-authors [content filterTerm]
                (map #(render-author-options (first %) filterTerm)
                content))


                But again, this a matter of taste. I will say though, if you have a iterable collection like content, it's a good idea to match Clojure convention and have it as the last parameter of the function. That way, you can do something like:



                (->> content
                (map some-transformation)
                (render-authors some-filter-term) ; Wouldn't be possible with your current order
                (filter some-predicate))


                Threading macros (-> and ->> mainly) are pretty common, and writing functions that work well with them will make your life easier. You didn't use and threading macros in your code, but I also don't see any good opportunities to use them either. I'd practice using them if you intend to keep writing Clojure, as they're exceedingly helpful.




                In get-highlighted-text-by-key, there's a few notable things.



                One, I'm not sure title is necessary here. It's needed in two places, and neither of those places seem to even need it. Unless my brain is just fried, (conj title (apply list nested-title)) is just conjoining (apply list nested-title) to an empty vector, which will just result in a single element vector. It would make more sense to just write:



                (vector (apply list nested-title))


                I also don't understand why you then wrap that in a (apply list, which just converts it to a list. You convert nested-title to a list with the first (apply list, then wrap it in a vector with (conj [], then convert the vector to a list with a second (apply list I'm not sure what you're trying to do here. It looks like that entire line could be reduced to just:



                (list (apply list nested-title))


                Although, I'll note that that plain lists aren't used very often unless you're writing macros. Lists are simple linked lists, so they can't do O(1) lookups by index. Use vectors in the vast majority of cases unless you have good reason to use something else (like you're writing a macro). Simply returning the lazy list that for evaluates to would likely be fine here. The caller can force the result if they want to.



                Second,



                (let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
                (if match


                (Besides being way too long of a line) is a perfect case to make use of if-let:



                (if-let [match (re-matches (re-pattern (str "(?i)(.*)(" filterTerm ")(.*)"))(apply str (get item key)))]
                (... If truthy, do this, using "match" ...)
                (... If falsey, do this, without having access to "match")


                if-let checks if the bound variable is truthy. If it is, it executes the first body with the truthy binding in scope. If it's falsey, it executes the second body, without the binding in scope.





                I think that's all the major things I can find. Good luck. Hopefully this was helpful.







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Mar 5 at 20:44

























                answered Mar 4 at 23:26









                CarcigenicateCarcigenicate

                3,84011632




                3,84011632



























                    draft saved

                    draft discarded
















































                    Thanks for contributing an answer to Code Review Stack Exchange!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid


                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.

                    Use MathJax to format equations. MathJax reference.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function ()
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f214726%2frss-feed-viewer-in-clojure%23new-answer', 'question_page');

                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    名間水力發電廠 目录 沿革 設施 鄰近設施 註釋 外部連結 导航菜单23°50′10″N 120°42′41″E / 23.83611°N 120.71139°E / 23.83611; 120.7113923°50′10″N 120°42′41″E / 23.83611°N 120.71139°E / 23.83611; 120.71139計畫概要原始内容臺灣第一座BOT 模式開發的水力發電廠-名間水力電廠名間水力發電廠 水利署首件BOT案原始内容《小檔案》名間電廠 首座BOT水力發電廠原始内容名間電廠BOT - 經濟部水利署中區水資源局

                    Prove that NP is closed under karp reduction?Space(n) not closed under Karp reductions - what about NTime(n)?Class P is closed under rotation?Prove or disprove that $NL$ is closed under polynomial many-one reductions$mathbfNC_2$ is closed under log-space reductionOn Karp reductionwhen can I know if a class (complexity) is closed under reduction (cook/karp)Check if class $PSPACE$ is closed under polyonomially space reductionIs NPSPACE also closed under polynomial-time reduction and under log-space reduction?Prove PSPACE is closed under complement?Prove PSPACE is closed under union?

                    Is my guitar’s action too high? Announcing the arrival of Valued Associate #679: Cesar Manara Planned maintenance scheduled April 23, 2019 at 23:30 UTC (7:30pm US/Eastern)Strings too stiff on a recently purchased acoustic guitar | Cort AD880CEIs the action of my guitar really high?Μy little finger is too weak to play guitarWith guitar, how long should I give my fingers to strengthen / callous?When playing a fret the guitar sounds mutedPlaying (Barre) chords up the guitar neckI think my guitar strings are wound too tight and I can't play barre chordsF barre chord on an SG guitarHow to find to the right strings of a barre chord by feel?High action on higher fret on my steel acoustic guitar