It would be nice to use this in a more idiomatic Clojure way, but one of the principals of Clojure is to provide access to all the Java libraries without having to write Clojure wrappers for all of them. So one way to accomplish this is with the clojure bean function. It takes a java bean and returns a Map with the values for all of the getters in the bean. We can use this to use the URI in an idiomatic way that looks more like the Ruby version:
It's really good to see a book being published by the Pragmatic Programmers on the Stripes, even if it is too little, too late. I wrote an article on Stripes a few years ago, and actually, I still like how Stripes handles mapping request parameters to an object graph, but since then I have discovered the benefits of dynamic languages like Ruby, so I would never consider using a framework like Stripes, just because it means programming in Java. When I was doing Java, I felt that Stripes was a much better framework than WebWork/Struts, but for some reason flew under the radar. In fact, this blog is built using Stripes. Stripes' creator Tim Fennell always did an excellent job answering questions on the mailing list and deserved more recognition from the Java community as a whole.
It's surprising to see this book coming out now. Frankly, straight Java as a web development language is yesterday's technology, the Cobol of our generation. The JVM is still alive an well, with many great options, such as JRuby, Groovy, Scala and the best of them all, Clojure. I would love to see a Pragmatic Programmer book on Clojure, just to give some more attention to Clojure, because it is such a great language. Anyway, congrats to Tim Fennell, Frederic Daoud and the Stripes team on finally getting some acknowledgment for building a great framework and a great community.
I just posted a short screencast on how to get started with Clojure and Aquamacs:
One of the fun features of Clojure, or any Lisp I suppose, is the interactive development workflow. As you can see in the video, you write code by creating expressions that define functions and then you evaluate those functions in the REPL (Read-Eval-Print-Loop). You can redefine a function at any time. You can imagine that if you had a production system running, you could connect to it via a REPL or something like that, evaluate some expressions that redefine functions that contain bugs, and the system would be fixed with no downtime.
Here's the contents of the ~/bin/clj script:
#!/bin/bash
SRC_DIR=/Users/pbarry/src
CLOJURE_JAR=$SRC_DIR/clojure/clojure.jar
JLINE_JAR=$SRC_DIR/jline/jline-0.9.94.jar
if [ -z "$1" ]; then
java -cp $JLINE_JAR:$CLOJURE_JAR jline.ConsoleRunner clojure.lang.Repl
else
java -cp $CLOJURE_JAR clojure.lang.Script $1
fi
One problem is that running it from the command line is a lot of typing and how do a run code I have saved in a file? Something better will probably come along eventually, but here's what I did. First, save closure in /usr/local/lib/closure. Then, copy JLine jar to that directory. Finally, create a shell script at /usr/local/bin/clj with the following contents:
#!/bin/bash
CLOJURE_DIR=/usr/local/lib/clojure
CLOJURE_JAR=$CLOJURE_DIR/target/clojure-lang-1.0-SNAPSHOT.jar
if [ -z "$1" ]; then
java -cp $CLOJURE_DIR/jline-0.9.93.jar:$CLOJURE_JAR \
jline.ConsoleRunner clojure.lang.Repl
else
java -cp $CLOJURE_JAR clojure.lang.Script $1
fi
So with this, assuming /usr/local/bin is in your path, you can either run a script of invoke the REPL:
#run foo.clj
clj foo.clj
#start the REPL
clj
Now that we've got that out of the way, I thought I would provide a slight variation of the Runtime Polymorphism example:
So refer to the documentation on Runtime Polymorphism for a full explanation of what's going on here, but here's my short summary. When you call encounter, the defmulti is called first. The defmulti returns a data structure that is then used to dispatch to a defmethod that matches that data structure. The defmulti uses the value that corresponds to the key :Species in the map to construct that data structure, but the defmethod doesn't know or care where that value came from. The first list in the defmethod for encounter is what should be matched. In other words, if the defmulti returns [:foo :bar], then it calls defmethod encounter [:foo :bar]. The second list is the declaration of the arguments that the method takes.
Another little nugget in there is that the comma is whitespace. Maps are defined as a list of alternating key values, surrounded by curly braces. So {:a 1 :b 2 3 :c :d 4} is a map, but as you can see, sometimes without the commas, it can be confusing. So you can write is as {:a 1, :b 2, 3 :c, :d 4} to make it more clear where the pairs are, but it's not required.