tags:

views:

1608

answers:

8

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.

+13  A: 

This 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
Brian Carper
Bragging comment, I know, but allow me for once to take a position. How can people like this language? it's stylistically unreadable :(
Stefano Borini
Anything is readable if you spend enough time with it. Proper syntax highlighting helps (Stack Overflow's highlighter script chokes on Lisp). Many people have a color scheme that fades the parens into the background a bit. This code is also an odd example because of the huge string I had to left-justify (and because I was trying to cram everything into 50ish lines).
Brian Carper
I wish to rebut. the language that must not be named (brainf*k) stays unreadable in any case, regardless of how much time you spend on it. However, I tried to import your script in vim with proper highlighting. It helped, but not much. The main problem about functional languages is that they are intrinsically deeply nested, and deep nesting is by many parts (GUI design, refactoring, code smells) reported as a warning sign for bad usability or code quality. The functional approach is cool (would never leave out map() from python, ever!), but it must be used with moderation, IMHO.
Stefano Borini
Lisp is admittedly dense, but that lets you put more code on-screen at once, which makes it easy to scan through lots of code quickly. So there are pluses and minuses. There are also structures that you grow to recognize; `let` forms always look a certain way for example. This is all largely subjective and I can't remark on your experiences, but Lisp was painful for me to read at first too, and after a year or two I have no problems. I've read many other people's experiences who gasped in horror at Lisp at first, but eventually grew to like or even prefer its syntax. YMMV of course.
Brian Carper
Thank you Brian, this has made its way into my utilities, so I do not have to decontextualize by opening up Dashboard or, gasp, look out the window to know what it's like outside!
Pinochle
I think the biggest problem is that /you/ have trouble reading Lisp. I, nor Brian, nor 99% of the people who have actually /used/ Lisp on #Clojure and #lisp on freenode have a problem reading Lisp.
Rayne
+3  A: 
Steven
…for certain values of "useful," I guess.
Chuck
This is a great site!
RED SOFT ADAIR
+3  A: 

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)
Brian Carper
ImageMagick + wget?
Dave Jarvis
My code works on the JVM without any other dependencies, which is its main appeal. I needed this for a website I was making. It's easier to do this than to validate URLs myself and do system calls and check return values etc. The Java versions throw exceptions when they fail and deal with standard Java objects I can do other things with (pass easily into other libraries etc.). I also tried the ImageMagick bindings for Java but they were pretty painful.
Brian Carper
+6  A: 

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.

Jeff
A: 

(println "Hello, world!")

Leo Jweda
(. javax.swing.JOptionPane (showMessageDialog nil "Hello World")); Add a little Java?
That would have definitely been my answer but the question is about Clojure..Psst: I took that from Wikipedia; I don't know Clojure! :D
Leo Jweda
+2  A: 

Clojure probably has a power function, but I was really excited when I figured this out:

(defn pow [base exp] (reduce * (replicate exp base)))
Ronald
You're right, it's in java.lang.Math: `(defn pow [base exp] (Math/pow base exp))`
nilamo
But that's still cool.
nilamo
A: 

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.

KL
+3  A: 

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))
clartaq