Clojure seems likes it might have a good shot at being a popular Lisp. I was wondering how many people have actually adopted it to solve some of the small, yet real, problems that they have encountered. Since Clojure doesn't have an entry in Pleac, I thought that it would be great if people posted their small solutions to problems that they've solved in Clojure.
views:
1608answers:
8This prints a weather forecast via Yahoo! Weather.
(ns weather
(:use (clojure [xml :only [parse]] [zip :only [xml-zip]])
(clojure.contrib duck-streams str-utils pprint)
(clojure.contrib.zip-filter xml)))
(defn fetch-xml [uri]
(xml-zip
(parse
(org.xml.sax.InputSource.
(java.io.StringReader.
(slurp* (java.net.URI. (re-gsub #"\s+" "+" (str uri)))))))))
(defn yahoo-weather
([loc-code] (yahoo-weather loc-code "c"))
([loc-code unit]
(let [rss (fetch-xml (str "http://weather.yahooapis.com/forecastrss?p=" loc-code "&u=" unit))]
(if (= (text (xml1-> rss :channel :item :title)) "City not found")
"City not found. Go to http://weather.yahoo.com/, search for your city, and look in the URL for the location code."
(let [[units loc wind atm ast] (map #(xml1-> rss :channel (keyword (str "yweather:" %)))
["units" "location" "wind" "atmosphere" "astronomy"])
conditions (xml1-> rss :channel :item :yweather:condition)
date (re-find #"\d+:\d+.*" (xml1-> rss :channel :item :pubDate text))
fors (xml-> rss :channel :item :yweather:forecast)]
(cl-format true
"Weather for ~a, ~a (~a)
Temperature: ~a\u00B0 ~a
Wind Chill: ~a\u00B0 ~a, ~a ~a
Conditions: ~a
Humidity: ~a%
Barometer: ~a ~a
Sunrise/Sunset: ~a / ~a
Forecast:
~{ ~{~a: ~a. Hi ~2d, Lo ~2d.~}~^~%~}
"
(attr loc :city) (attr loc :region) date
(attr conditions :temp) (attr units :temperature)
(attr wind :chill) (attr units :temperature) (attr wind :speed) (attr units :speed)
(attr conditions :text)
(attr atm :humidity)
(attr atm :pressure) (attr units :pressure)
(attr ast :sunrise) (attr ast :sunset)
(map #(list (attr % :day)
(attr % :text)
(attr % :high)
(attr % :low))
fors)))))))
For example:
user> (weather/yahoo-weather "CAXX0328")
Weather for North Vancouver, (10:00 am PDT)
Temperature: 14° C
Wind Chill: 14° C, 8.05 kph
Conditions: Light Rain Shower
Humidity: 88%
Barometer: 1018 mb
Sunrise/Sunset: 6:01 am / 8:32 pm
Forecast:
Thu: Few Showers. Hi 18, Lo 12.
Fri: AM Showers. Hi 19, Lo 12.
nil
This creates a thumbnail from an image. The image can be a local File, remote URL or anything else javax.imageio.ImageIO
can read (thanks Java!). Output can be any image format javax.imageio.ImageIO
can write.
(use '(clojure.contrib java-utils)) (defn make-thumbnail "Given an input image (File, URL, InputStream, ImageInputStream), output a smaller, scaled copy of the image to the given filename. The output format is derived from the output filename if possible. Width should be given in pixels." ([image out-filename width] (if-let [format (re-find #"\.(\w+)$" out-filename)] (make-thumbnail image out-filename width (nth format 1)) (throw (Exception. "Can't determine output file format based on filename.")))) ([image out-filename width format] (let [img (javax.imageio.ImageIO/read image) imgtype (java.awt.image.BufferedImage/TYPE_INT_RGB) width (min (.getWidth img) width) height (* (/ width (.getWidth img)) (.getHeight img)) simg (java.awt.image.BufferedImage. width height imgtype) g (.createGraphics simg)] (.drawImage g img 0 0 width height nil) (.dispose g) (javax.imageio.ImageIO/write simg format (as-file out-filename)))))
Create a JPG thumbnail from a local PNG:
(make-thumbnail (java.io.File. "some-image.png") "thumb.jpg" 150)
Create a GIF thumbnail from a remote JPG:
(make-thumbnail (java.net.URL. "http://blog.stackoverflow.com/wp-content/uploads/justice-league-small.jpg") "small.gif" 250)
Not really particularly useful by itself, but the idea is similar to JSON in Javascript--you can move Clojure data structures to and from the file system. Adopted from Practical Common Lisp's Database example:
(ns storage (:import (java.io File PushbackReader FileReader FileWriter)))
(defn load-data
"Loads data from the given file."
[filepath]
(do
;; the let block creates the file if it doesn't exist
;; reader throws an exception if there's no parsable data struct
(let [file (new File filepath)]
(if (not (.exists file))
(do
(.createNewFile file)
(doto (new FileWriter filepath) (.write "{}") .close))))
(read (new PushbackReader (new FileReader filepath)))))
(defn dump-data
"Exports data structure to a file."
[filepath data]
(doto (new FileWriter filepath) (.write (str data)) .close))
Example usage:
user=> (dump-data "test.dat" {:a [1 2 3] :b "hello" :c true})
#<FileWriter java.io.FileWriter@186df0f>
user=> (load-data "test.dat")
{:a [1 2 3], :b "hello", :c true}
Certainly beats writing your own (complex) save mechanism for your program. I'm sure reading purely from a string is possible just by changing some of the readers provided via Java.
Clojure probably has a power function, but I was really excited when I figured this out:
(defn pow [base exp] (reduce * (replicate exp base)))
Writing Swing apps, the JMenuBar stuff is always annoying. Thanks to dorun/map it's much easier:
(let [menus
[
{:name "File" :mnemonic \F
:items
[
{:name "Open" :mnemonic \O :fn file-open}
:separator
{:name "Exit" :mnemonic \x :fn file-exit}
]
}
{:name "Help" :mnemonic \H
:items
[
:separator
{:name "About..." :mnemonic \A :fn help-about}
]
}
]
menu-fns
(into
{}
(mapcat
(fn [menu]
(map
(fn [item] [(:name item) (:fn item)])
(:items menu)))
menus))
ui-frame
(proxy [JFrame ActionListener] ["UI Frame"]
(actionPerformed
[event]
(let [command (.getActionCommand event)
menu-fn (get menu-fns command)]
;; Handle menu commands
(if menu-fn
(apply menu-fn [this]))
))
)
]
(defn new-menu [listener]
(let [menubar (JMenuBar.)]
(dorun
(map
(fn [x]
(let [menu (JMenu. (:name x))]
(.setMnemonic menu (int (:mnemonic x)))
(.add menubar menu)
(dorun
(map
(fn [item]
(if (= :separator item)
(.addSeparator menu)
(let [menu-item
(if (:mnemonic item)
(JMenuItem. (:name item) (int (:mnemonic item)))
(JMenuItem. (:name item)))]
(.addActionListener menu-item listener)
(.add menu menu-item))))
(:items x)))))
menus))
menubar))
Right now I don't need sub-menus, but it's a trivial change to new-menu
to get them. Also adding icons, active/inactive state, etc. is just more fields in menus
.
The most useful thing I've written for myself in Clojure is the almost trivial function:
(defn tally-map
" Create a map where the keys are all of the unique elements in the input
sequence and the values represent the number of times those elements
occur. Note that the keys may not be formatted as conventional Clojure
keys, i.e. a colon preceding a symbol."
[aseq]
(apply merge-with + (map (fn [x] {x 1}) aseq)))
I use this all the time in the work I do. Very useful for histograms.
Brian Carper was kind enough to suggest the following improved form of the function.
(defn tally-map [coll]
(reduce (fn [h n]
(assoc h n (inc (or (h n) 0))))
{} coll))