Finding Clojure
Kurt Harriger @kurtharriger
The Challenge
Twitter search url filter
├── README├── project.clj├── src│ └── twitturl│ └── core.clj└── test └── twitturl └── test └── core.clj
Project Folder% lein new twitturlCreated new project in: /Users/kurtharriger/code/twitturl
% git init && git add -A && git commit -m “create project”
(defproject twitturl "1.0.0-SNAPSHOT" :description "Twitturl aggregator" :dependencies [[org.clojure/clojure "1.2.0"] [org.clojure/clojure-contrib "1.2.0"] [ring "0.3.7"] [clj-http "0.1.2"]] :dev-dependencies [[lein-ring "0.4.0"]] :ring {:handler twitturl.core/app})
project.clj
Appleholics% brew install leiningen
Non AppleholicsFind Leiningen for Clojure on
https://github.com/technomancy/leiningen
% lein self-install
Clojure:(defn fn [arg1 & more] arg1)
Clojure: (println (fn value1 value2))
Java:System.out.println(fn(value1, value2));
S-Expressions
defn is a macro used here with 3 parameters: function name, arguments, and body. Returns the function binding.
Clojure:(defn fn [arg1 & more] arg1)
Clojure: (println (fn value1 value2))
Java:System.out.println(fn(value1, value2));
S-Expressions
defn is a macro used here with 3 parameters: function name, arguments, and body. Returns the function binding.
(ns twitturl.core (:require [com.twinql.clojure.http :as http]) (:use [ring.middleware.params]) (:use [hiccup.core]))
(defn handler [] nil) ;todo create handler
(def app (-> handler wrap-params))
ring framework
Import dependencies
Entry point-> operator used apply multiple
functions to handler
(defn format-tweets [json] nil) ; todo
(defn handler [{{q "q"} :params}] { :content-type "text/html" :body (html [:body
(-> (search q) format-tweets)])})
same as:
(defn handler [request] (let [params (request :params) q (params "q")] {:content-type "text/html" :body (html [:body
(-> (search q) format-tweets)])}))
De-structuring
Local variable assignments request and params
unnecessary.
Only need q
(def url "http://search.twitter.com/search.json")
(defn search [query] (http/get url :query {:q query} :as :json))
The Search
returns result of com.twinql.clojure.http.get using parameter list1) url 2) :query 3) {:q query } 4) :as 5) :json
define readonly variable named url
% lein deps Copying 22 files to /Users/kurtharriger/code/twitturl/libCopying 17 files to /Users/kurtharriger/code/twitturl/lib/dev
% lein replREPL started; server listening on localhost:24319.user=> (use ‘twitturl.core)niluser=> (search “clojure”)
{:code 200, :reason "OK", :content {:results [{:from_user "planetclojure", :text "Removing defined tests in Clojure REPL http://goo.gl/fb/qeUty #clojure #SO", ... }, ...] }}
Enter the repl
(defn format-tweets [json] nil) ; todo
(defn handler [{{q "q"} :params}] { :content-type "text/html" :body
(html [:body (-> (search q) format-tweets)])})
The Handler
returns a map containing :content-type and :body
hiccup library function
same as (format-tweets (search q))
(defn aslink [url] (html [:a {:href url} url]))
user=> (aslink “http://url1”)"<a href=\"http://url1\">http://url1</a>"
(def urls (partial re-seq #"http://\S+"))same as:(defn urls [text] (re-seq #“http://\S+” text))
user=> (urls “blah blah http://url1 http://url2”)("http://url1" "http://url2")
functions
(defn linkify [text] (reduce #(.replace %1 %2 (aslink %2)) text (urls text)))
String linkify(String text) { String newText = text; for(String url : getUrls(text)) { newText = newText.replace(url, asLink(url)); } return newText;}
map reduce
(fn [newText url] (.replace newText url (aslink url))
(defn format-tweet [tweet] [:li (:from_user tweet) [:blockquote (-> tweet :text linkify)]])
chaining
Java:
linkify(tweet.get(“text”))
1 2
(defn format-tweet [tweet] [:li (:from_user tweet) [:blockquote (-> tweet :text linkify)]])
chaining
4
2
Java requires more(parentheses)?!
Java:
linkify(tweet.get(“text”))
1 2
(defn format-tweets [json] [:ul (->> json :content :results (remove #(-> % :text urls nil?)) (map format-tweet))])ArrayList<String> formatTweets(JSONObject json) {
StringBuilder tweets = new StringBuilder();tweets.append(“<ul>”);JSONObject content = json.getJSONObject(“content”);JSONArray results = json.getJSONArray(“results”);for(JSONObject tweet : results) { String[] urls = getUrls(tweet.getString(“text”)) if(urls != null && urls.length > 0) {
tweets.append(“<li>” + formatTweet(tweet) “</li>”); }}tweets.append(“</ul>”);return tweets;
}
chaining
(defn format-tweets [json] [:ul (->> json :content :results (remove #(-> % :text urls nil?)) (map format-tweet))])ArrayList<String> formatTweets(JSONObject json) {
StringBuilder tweets = new StringBuilder();tweets.append(“<ul>”);JSONObject content = json.getJSONObject(“content”);JSONArray results = json.getJSONArray(“results”);for(JSONObject tweet : results) { String[] urls = getUrls(tweet.getString(“text”)) if(urls != null && urls.length > 0) {
tweets.append(“<li>” + formatTweet(tweet) “</li>”); }}tweets.append(“</ul>”);return tweets;
}
chaining5
12
Java usually requires more(parentheses)?!
% lein ring server2011-04-16 21:18:54.965:INFO::Logging to STDERR via org.mortbay.log.StdErrLog
2011-04-16 21:18:54.968:INFO::jetty-6.1.26
2011-04-16 21:18:54.998:INFO::Started [email protected]:3000
Started server on port 3000
% lein ring warCreated /Users/kurtharriger/code/twitturl/twitturl-1.0.0-SNAPSHOT.war
Amazon Elastic Beanstalk pluginhttps://github.com/weavejester/lein-beanstalk
ring server
1 (ns twitturl.core 2 (:use [ring.middleware.params]) 3 (:use [hiccup.core]) 4 (:require [com.twinql.clojure.http :as http])) 5 6 (def url "http://search.twitter.com/search.json") 7 8 (defn search [query] (http/get url :query {:q query} :as :json)) 9 (defn aslink [url] (html [:a {:href url} url])) 10 11 (def urls (partial re-seq #"http://\S+")) 12 13 (defn linkify [text] 14 (reduce #(.replace %1 %2 (aslink %2)) text (urls text))) 15 16 (defn format-tweet [tweet] 17 [:li (:from_user tweet) 18 [:blockquote (-> tweet :text linkify)]]) 19 20 (defn format-tweets [json] 21 [:ul (->> json :content :results 22 (remove #(-> % :text urls nil?)) 23 (map format-tweet))]) 24 25 (defn handler [{{q "q"} :params}] 26 { :content-type "text/html" 27 :body (html [:body (-> (search q) format-tweets)])}) 28 29 (def app (-> handler wrap-params ))
src/twitturl/core.clj
Finding Clojure
Kurt Harriger @kurtharriger
DSLs
High-order functions
Lists are awesome
Less () than Java
Method chaining
Leiningen Ring framework
https://github.com/kurtharriger/twitturl
De-structuring