Showing posts with label Clojure. Show all posts
Showing posts with label Clojure. Show all posts

Wednesday, December 22, 2010

My first open source project

Well, I finally did it.  I actually have a project that's hosted on https://bitbucket.org/Dauntless/sojourner (see my links to the left)

I decided to put up the work I've been doing on my clojure project there.  Eventually I will probably move it to github, but I know at least some of mercurial, while the only thing I know about git is how to clone something.  Unfortunately I don't think bitbucket has a good open-source plan like github does (hmmm, maybe that's why git is wining the DCVS contest).

Basically this project is first and foremost a learning project for me on many levels.  I've never done an open source project before...not even as a commiter.  Secondly, this will help me learn clojure.  Thirdly, it will hopefully have at least some practical value.  And finally, I wish to document this very well so that others can also learn from it.

This project has one major philosophical mantra:
1.  If it's not documented, it's a bug

I'm not just talking about API documentation either.  I like projects that have high level guides, tutorials or other documentation.  I find most API documentation next to useless (and I will point my finger here at clojure itself).  I will take a project that has less features but better documentation than a project with more features but little documentation.  It's a sad fact that most open source projects have poor docs.

I would also like the project to follow a test driven development philosophy, but honestly, I'm not sure how I am going to do this for a gui program (selenium?).  I also have to be honest and say that I still haven't yet wrapped my head around TDD despite being  Test Automation Engineer.  TDD makes sense when your API is already solidified, and therefore you have a spec to test against.  But how do you create tests when you are still exploring and shaping up the API?

Nevertheless I am stoked on this.  Even if no one else uses it, it is still a great learning tool for me.

Tuesday, December 21, 2010

Clojure Launcher pt. 2; now with ActionListeners!

I finally got a chance to work some more on the Clojure launcher GUI program.  I refactored a lot of the code and decided to make use of some closures.  When I started looking at my code, it actually seemed like a macro might be more useful, and I'll explain why I think so in a little bit.

But here so far is my new code.

   1 (ns sojourner.gui
   2   (:import (javax.swing JPanel JButton JLabel JFrame JTextField JFileChooser)
   3            (java.awt.event ActionListener)
   4            (net.miginfocom.swing MigLayout)))
   5 
   6 (def jpanel (JPanel. (MigLayout.)))
   7 
   8 ; Create a file chooser that will pop-up if the user clicks
   9 ; on any of the Browse buttons
  10 (def fc (JFileChooser.))
  11 
  12 ;; This is the function that will be called when any of the Browse buttons are clicked
  13 ;; Basically, we open up a JFileChooser dialog, and we capture the file/directory
  14 ;; that the user selects.  We then append this to the JTextField
  15 (defn get-file-selection [ ]
  16     (let [ fc (JFileChooser.) 
  17            retval (.showOpenDialog fc nil) ]
  18        (if (= retval JFileChooser/APPROVE_OPTION)
  19           (do 
  20              (println (.getSelectedFile fc))
  21              (.getSelectedFile fc))
  22            "")))
  23 
  24 ; Create a proxy for clicking on the Browse button
  25 (def on-click (proxy [ActionListener] []
  26                 (actionPerformed [ event ] (get-file-selection))))
  27                     
  28 
  29 (defn make-row-fn 
  30     [ panel lbl-string fld-string btn-string ]
  31     (fn [ panel ]
  32       (let [btn (JButton. btn-string) ]
  33          (.addActionListener btn on-click)
  34          (doto panel
  35            (.add (JLabel. lbl-string))
  36            (.add (JTextField. fld-string 10))
  37            (.add btn "wrap" ) ) ) ) )
  38 
  39 ;; It seems to me that all these make-row functions could be encapsulated
  40 ;; inside of a macro, or at the least, return a function which does the following
  41 (def make-ext-jar-row (make-row-fn jpanel "External Jars" "" "Browse"))
  42 (def make-classpath-row (make-row-fn jpanel "Class path" "" "Browse"))
  43 (def make-clojars-row (make-row-fn jpanel "Clojure files" "" "Browse"))
  44 
  45 
  46 (defn make-launcher [ panel ]
  47    ( let [p (doto panel
  48              (make-ext-jar-row )
  49              (make-classpath-row )
  50              (make-clojars-row )) ]
  51      ; return the frame after we're done here
  52      (doto (JFrame. "Java Launcher")
  53        (.setContentPane p)
  54        (.setSize 300 150)
  55        (.setVisible true))))
  56 
  57 (def frame (make-launcher jpanel))
  58 
  59  
 
 And this is what it looks like


Yeah I know, it's not a lot to look at, but now if you click the Browse button, a JFileChooser dialog will appear. However, there's still a lot to do:

1. Get the file selected, and enter it in the corresponding JTextField
2. Rework the make-row-fn as a macro, so that each button can have a different proxy
3. I'm going to rework this to use leiningen (clojure's version of maven)
4. A new JTextArea which will contain either the repl launched or the running program
5. Some fields to enter the jar or main to run (eg java -jar myjar.jar)

 

Monday, December 20, 2010

First stab at creating a Clojure/Java launcher with Swing

One pain I hate about java is that I have to tell the JVM my classpath (and clojure path) in order to run my program.  Unlike python, where I can do an import wherever I want, and I can even dynamically add a path for the python interpreter to search for modules, there's no such thing for Java.  Basically you tell java where all the jars, .class files and .clj files are when you first launch the JVM, or it won't be able to find them.

Because of this pain in the butt, I want to create a Swing app where the user can select any external jars, .class files, paths to .class files, or .clj files so that launching an app will be easier.  I may even add some options for special JVM args to pass in.  And if I get REALLY fancy, maybe I'll even add a text area for my own repl and use either clojure.contrib.shell or clojure.contrib.server-socket.  If I don't do this, then I'll need to launch a subprocess with a new terminal.


I actually just discovered that clojure has some special miglayout support, as I was going to use this layout manager for my GUI, since that seems to be the most "hand-coded friendly".  However, I already implemented a little code already, mostly due to reading some of Stuart Sierra's blog on writing Swing apps with clojure.

But here's a first stab at this.  Remember when you launch clojure, that you'll need to include the miglayout jar in your classpath.  By the way, one thing I have not liked about ANY of the books I have read on clojure is in explaining the synatx for importing (use, require, or import) libraries.  Sometimes you have to quote a list, sometimes you don't, and no one gives any good explanation on when they are required.

(ns sojourner.gui
  (:import (javax.swing JPanel JButton JLabel JFrame JTextField)
               (net.miginfocom.swing MigLayout)))


(defn make-launcher []
  (let [ ext-jar-lbl (JLabel. "External Jars")
          ext-jar-field (JTextField. "Add paths here")
          panel (doto (JPanel. (MigLayout.))
                      (.add ext-jar-lbl)
                      (.add ext-jar-field "wrap") ) ]
        ; return the frame after we're done here
        (doto (JFrame. "Java Launcher")
          (.setContentPane panel)
          (.setSize 300 150)
          (.setVisible true))))

(def frame (make-launcher))


I tested this out in my repl by using the load function (I named this file gui.clj, and don't forget to add this to your classpath). This is pretty cool, as you can then use the (use :reload 'gui) function to make changes to the code above.


Right now, all it shows is a frame with a label and a JTextField.  It isn't even listening to any events yet, but it's a start.

Sunday, November 28, 2010

Learning Clojure...a newbies thoughts

I spent a good deal of my vacation this week learning, or I should say re-studying Clojure.  I bought Stuart Halloway's "Programming Clojure" book earlier this year, and I read through almost all of it except the chapter on Macros.  However I found learning Clojure to be a fairly difficult process.  However with the help of two new MEAP books from Manning.com, I am on track to becoming Clojure fluent (I hope)


In college, I took an Artificial Intelligence class, and we did have to write a few of our programs in lisp (Common Lisp), but to be honest, I'm not sure how I was able to do it because at the time it made no sense to me.  Lisp has a horrible reputation among many software engineers, and yet ironically, it is also held up as something of a pinnacle.  For example, a coworker at my job showed me a list of the "hierarchy" of programmers.  At  the top, the spot was shared by lisp and assembly programmers (I guess the list was made before VHDL programming became more common?), and at the bottom were people who insisted that HTML was a language.


But despite some people who acknowledge the power of lisp, quite a few others either know little of lisp, or they relegate it to the land of so-called dead/dying languages like Cobol, Fortran and Ada.  And of course, many people who have ventured into lisp territory refuse to touch it again due to the Lots of Insipid and Superfluous Parentheses (LISP).


However, I think the syntax (or lack thereof) of a lisp dialect is simple bias.  I remember when I first started learning python, how stupid it was (I thought) to make white space matter.  I had that opinion for all of about 3 days, and then I just got used to it, and now I love it.  I'm now feeling the same way about Clojure and the "strange" syntax.  And Clojure is better than most lisps and schemes because they added some new data types which don't use parentheses (it uses [] to denote vectors, and {} to denote maps).  Moreover, after reading chapter 1 of "Clojure In Action" by Amit Rathore, I have a better appreciation for why the syntax is the way it is.


But I think another reason many people give up or don't even want to try learning a lisp dialect is because it is so different in its paradigm.  Clojure is a functional language for the most part, and getting used to immutable data, and not having first-class variables is a HUGE leap for most people including myself.  I'm sort of getting past the mutable data thing (just return a new data structure instead of mutating an existing data sturcture), the idea of not having what most programmers think of as variables is still something I'm grappling with.  


Yet another stumbling block for programmers used to mutable state and imperative programming is their bias against recursion.  "What do you mean there's no for loop?".  Well, actually, there is a for function in Clojure, but it's not a loop, it's a sequence comprehension
(sort of like a python list comprehension, but more abstract...for a loop, you'd probably want a doseq macro instead).  One of my coworkers is a recent CS grad, and he was told by his professor that recursion is generally frowned upon.



While many languages do not optimize recursion, and recursion can be dangerous due to its consuming of call/stack frames, that doesn't mean it's always bad.  Recursion can ALWAYS be used to implement a loop (if the iterations are small enough).  And better yet, depending on the language and how you implement the recursion, the code might wind up generating a for-loop for you anyway (such as with languages that can do tail call optimization).  For example, Clojure's loop/recur form will do this as opposed to direct self-recursion.  Until recently, I considered myself very bad with recursion, and I know that others also have some problems with understanding it.  But due to a lot of practice at my job with it including some mutual recursive functions, I have gotten a bit better at it.  There is a symmetrical elegance to recursion that is lacking in for loops and not to mention that for and while loops can be dangerous due to mutating the state of some iterator or sentinel variable.


So slowly and surely I am starting to "get" Clojure, and thus some of the legacy of lisp.  I even had a small and temporary epiphany when I understood what is meant by "code is data".  Lisp was designed to compute symbols, not numbers, and lisp code has the same form as data.  I briefly envisioned the clojure reader as consuming structures...not code.  But that in turn helped me understand Macros a little better, and indeed one of my next tasks is to write a small macro.


Most importantly though, I find programming in clojure fun.  I honestly haven't felt this way since I learned python.  I picked up a book on Scala, but for some reason, it didn't really catch my interest (maybe because they chose the perl philosophy of "there is more than one way to do it"....for example, you 'usually' don't have to put semi-colons at the end of a statement or expression...huh?).  That's not to say I might not pick it up again after I get good with Clojure, but it's on the backburner now.  And perl?  Let's just say that I hope I never have to program in perl again.  No introspection, no built-in shell, no real exception handling (eval'ing is not exception handling), bolted on joke of a OO system, TIMTOWTDI (ummm, why are there at least three different ways to pass arguments to a function?) are just things I don't want to have to deal with again (and if you love perl...great for you...but it was not great for my own work style)




So in my quest to get better at Clojure, I have decided to do three things:
1.  Create a "computer assisted" role playing game (of the pen and paper sort, not MMORPG)
2.  Create a blog detailing my experiences learning Clojure
3.  Go over some classic CS problems in Clojure


For the first goal, there's no better teacher than practice.  So why not do something fun while I am at it?  It will also help me relearn Swing which I haven't used in years.


The second goal is so that I can help others learn from my mistakes or to just get a perspective from a fellow n00b who might be just a little farther down the path.  My uncle, who was a Harvard law grad once told me that he learned more from his fellow students than he did his professors.  Sometimes when you already know something, it's hard to put yourself back in the shoes of someone just learning.  So hopefully having a blog detailing my experiences will be of help.


And finally, I do want to go back and start taking Master's degree classes maybe next Fall.  I realize how weak some of my basics have gotten (analysis of algorithms, automata theory, and especially my math).  So going back and reimplementing some classic algorithms in clojure might help jump-start my brain again.