6 Replies - 6523 Views - Last Post: 11 June 2011 - 01:55 PM

#1 Raynes   User is offline

  • D.I.C Lover
  • member icon

Reputation: 615
  • View blog
  • Posts: 2,815
  • Joined: 05-January 09

Fun with Clojure #1

Post icon  Posted 11 May 2011 - 11:15 AM

*
POPULAR

This is the first thread in a series of threads that I'll do every now and then if DiCers enjoy it.

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.

Attached image(s)

  • Attached Image


Is This A Good Question/Topic? 8
  • +

Replies To: Fun with Clojure #1

#2 Shane Hudson   User is offline

  • D.I.C Technophile
  • member icon

Reputation: 345
  • View blog
  • Posts: 1,286
  • Joined: 06-December 09

Re: Fun with Clojure #1

Posted 11 May 2011 - 03:03 PM

That is really impressive, although the code looks dreadful to my un-clojure-trained eyes! Shame you made this topic clojure only, I would be interested in seeing similar challenges in haskell or similar.

Next task: Make the official DIC IRC quiz bot!
Was This Post Helpful? 0
  • +
  • -

#3 Raynes   User is offline

  • D.I.C Lover
  • member icon

Reputation: 615
  • View blog
  • Posts: 2,815
  • Joined: 05-January 09

Re: Fun with Clojure #1

Posted 11 May 2011 - 04:12 PM

That might actually be a fun idea. I could write a quiz plugin for my existing IRC bot, sexpbot. I'll note that and make it one of my next Fun With Clojure projects.

I would have expanded this to Haskell, but I'm not really familiar enough with Haskell to trust myself to write idiomatic code anymore.
Was This Post Helpful? 0
  • +
  • -

#4 ishkabible   User is offline

  • spelling expret
  • member icon





Reputation: 1747
  • View blog
  • Posts: 5,898
  • Joined: 03-August 09

Re: Fun with Clojure #1

Posted 13 May 2011 - 01:50 PM

hmmm, i kinda want to pick up my little Lisp Dijkstra's Algorithm code now... i gave it a rest a while back becuase Lisp didn't have the support for arrays that i wanted. once again, functional programing pushes me out of my shell. will it ever get me out there?
Was This Post Helpful? 0
  • +
  • -

#5 Raynes   User is offline

  • D.I.C Lover
  • member icon

Reputation: 615
  • View blog
  • Posts: 2,815
  • Joined: 05-January 09

Re: Fun with Clojure #1

Posted 13 May 2011 - 07:23 PM

I say keep trying. Chances are, if Common Lisp doesn't support the way you want to do something, you're probably doing it wrong anyway. There are always other options.
Was This Post Helpful? 0
  • +
  • -

#6 Shane Hudson   User is offline

  • D.I.C Technophile
  • member icon

Reputation: 345
  • View blog
  • Posts: 1,286
  • Joined: 06-December 09

Re: Fun with Clojure #1

Posted 31 May 2011 - 01:01 PM

Any fun with * version 2 coming?
Was This Post Helpful? 0
  • +
  • -

#7 Raynes   User is offline

  • D.I.C Lover
  • member icon

Reputation: 615
  • View blog
  • Posts: 2,815
  • Joined: 05-January 09

Re: Fun with Clojure #1

Posted 11 June 2011 - 01:55 PM

Yep. I'll get something out asap. I've got a few ideas. :)
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1