In these threads I'll post a Clojure application or piece of code that I've written that solves a real-world task. I'll try to do things that non-Clojurians would be interested in seeing -- things that are commonly done in non-FP languages that people might have trouble figuring out how to do in an FP language. This is a minor attempt from me to entice the non-FPers to use Clojure.
Go ahead and and reply with whatever comments you have about the code or this whole thing in general. Particularly, I would *really* like some suggestions on things that you would be interested in seeing written in Clojure. The idea here is to get you guys interested in the language, so your ideas are very important to me.
Furthermore, please don't ask code questions in this thread. If you have a problem with Clojure, please create a new thread with your question. That said, if you have a problem with the code *I* post particularly, let me know and I'll help you out.
Without further adieu, here is this weeks code: a very basic text editor.
(ns dic-texteditor.core (:use seesaw.core [clojure.java.io :only [file]]) (:import [javax.swing JFileChooser JEditorPane JScrollPane BorderFactory] java.awt.Font) (:gen-class)) (def current-file (atom (file (System/getProperty "user.home") ".dicscratch"))) (when-not (.exists @current-file) (spit @current-file "")) (defn add-border [l] (.setBorder l (BorderFactory/createEmptyBorder 1 1 1 1)) l) (def current-file-label (label :text @current-file)) (def editor (doto (JEditorPane.) (.setText (slurp @current-file)))) (def status-label (label :text "Your text. It goes there.")) (defn set-status [& strings] (.setText status-label (apply str strings))) (def main-panel (mig-panel :constraints ["fill, ins 0"] :items [[(JScrollPane. editor) "grow"] [status-label "dock south"] [(separator) "dock south"] [current-file-label "dock south"]])) (defn set-current-file [f] (swap! current-file (constantly f))) (defn select-file  (let [chooser (JFileChooser.)] (.showDialog chooser main-panel "Select") (.getSelectedFile chooser))) (defn a-new [e] (let [selected (select-file)] (if (.exists @current-file) (alert "File already exists. Stop being a bitch.") (do (set-current-file selected) (.setText editor "") (set-status "Created a new file."))))) (defn a-open [e] (let [selected (select-file)] (set-current-file selected)) (.setText editor (slurp @current-file)) (set-status "Opened " @current-file ".")) (defn a-save [e] (spit @current-file (.getText editor)) (set-status "Wrote " @current-file ".")) (defn a-save-as [e] (when-let [selected (select-file)] (set-current-file selected) (spit @current-file) (set-status "Wrote " @current-file "."))) (defn a-exit [e] (System/exit 0)) (defn a-copy [e] (.copy editor)) (defn a-cut [e] (.cut editor)) (defn a-paste [e] (.paste editor)) (def menus (let [a-new (action :handler a-new :name "New" :tip "Create a new file.") a-open (action :handler a-open :name "Open" :tip "Open a file") a-save (action :handler a-save :name "Save" :tip "Save the current file.") a-exit (action :handler a-exit :name "Exit" :tip "Exit the editor.") a-copy (action :handler a-copy :name "Copy" :tip "Copy selected text to the clipboard.") a-paste (action :handler a-paste :name "Paste" :tip "Paste text from the clipboard.") a-cut (action :handler a-cut :name "Cut" :tip "Cut text to the clipboard.") a-save-as (action :handler a-save-as :name "Save As" :tip "Save the current file.")] (menubar :items [(menu :text "File" :items [a-new a-open a-save a-save-as a-exit]) (menu :text "Edit" :items [a-copy a-cut a-paste])]))) (defn -main [& args] (add-watch current-file nil (fn [_ _ _ new] (.setText current-file-label (str new)))) (.setFont current-file-label (Font. "Sans Serif" Font/PLAIN 8)) (invoke-now (frame :title "DiC Example Text Editor" :content main-panel :on-close :exit :minimum-size [640 :by 480] :menubar menus)))
This application uses Seesaw, a new Clojure Swing wrapper, so the GUI is a Swing GUI. In some places, I've called the Swing API directly where Seesaw didn't support something in particular that I was trying to do. I'm happy about this, because it allowed me to demonstrate Clojure's seamless and natural Java interop.
I choose a text editor for a couple of reasons. Everybody writes them, they're relatively easy to write, and text editors aren't really a problem that allow for FP's benefits, for the most part, to come out. GUIs are generally non-functional things. I'm also keeping a little state (the current file at any given time) in an atom. This is is a good example of how Clojure can be a predominately functional programming language but still let you be a lesser degree of functional when you really need to.
You can run the editor yourself if you want. I've uploaded a standalone jar. If you have a JVM on your system, and you almost certainly do, then you don't need anything special to run the application. The jar packages Clojure and all my other dependencies in with it, so it is truly standalone. Just run it with the typical command: java -jar dic-editor.jar
Be careful when playing with it though. There are no "Are you sure you want to overwrite that file?" boxes and stuff. Also, I've only tested this on OS X and a friend made sure it ran on Linux, but it should work fine on Windows as well.
I've also attached a screenshot of the editor. Isn't the prettiest thing in the world, but I did the whole thing in 91 lines of Clojure.