Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Thursday, November 13, 2008

Clojure Baby Steps

Getting some baby steps going with Clojure. What threw me for a while is how difficult it was to get to do some basic output.

I wanted to see the JVM system properties. It's easy to get them, Java inter-operation is strong in Clojure, but in a readable format?

user=> (System/getProperties)
#=(java.util.Properties. {"java.runtime.name" "Java(TM) 2 Runtime Environment, Standard Edition", "sun.boot.library.path" "/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Libraries", "java.vm.version" "1.5.0_16-133", "awt.nativeDoubleBuffering" "true", "gopherProxySet" "false", "java.vm.vendor" "Apple Inc.", "java.vendor.url" "http://www.apple.com/", "path.separator" ":", "java.vm.name" "Java HotSpot(TM) Client VM", "file.encoding.pkg" "sun.io", "sun.java.launcher" "SUN_STANDARD", "user.country" "US", "sun.os.patch.level" "unknown", "java.vm.specification.name" "Java Virtual Machine Specification", "user.dir" "/Users/Howard", "java.runtime.version" "1.5.0_16-b06-284", "java.awt.graphicsenv" "apple.awt.CGraphicsEnvironment", "java.endorsed.dirs" "/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/endorsed", "os.arch" "i386", "java.io.tmpdir" "/tmp", "line.separator" "\n", "java.vm.specification.vendor" "Sun Microsystems Inc.", "os.name" "Mac OS X", "sun.jnu.encoding" "MacRoman", "java.library.path" ".:/Users/Howard/Library/Java/Extensions:/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java", "java.specification.name" "Java Platform API Specification", "java.class.version" "49.0", "sun.management.compiler" "HotSpot Client Compiler", "os.version" "10.5.5", "user.home" "/Users/Howard", "user.timezone" "", "java.awt.printerjob" "apple.awt.CPrinterJob", "file.encoding" "MacRoman", "java.specification.version" "1.5", "java.class.path" "/usr/local/clojure/clojure-contrib.jar:/usr/local/clojure/clojure-lang-1.0-SNAPSHOT.jar", "user.name" "Howard", "java.vm.specification.version" "1.0", "java.home" "/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home", "sun.arch.data.model" "32", "user.language" "en", "java.specification.vendor" "Sun Microsystems Inc.", "awt.toolkit" "apple.awt.CToolkit", "java.vm.info" "mixed mode", "java.version" "1.5.0_16", "java.ext.dirs" "/Users/Howard/Library/Java/Extensions:/Library/Java/Extensions:/System/Library/Java/Extensions:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/ext", "sun.boot.class.path" "/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/classes.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/ui.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/laf.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/sunrsasign.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/jsse.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/jce.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/charsets.jar", "java.vendor" "Apple Inc.", "file.separator" "/", "java.vendor.url.bug" "http://bugreport.apple.com/", "sun.io.unicode.encoding" "UnicodeLittle", "sun.cpu.endian" "little", "mrj.version" "1050.1.5.0_16-284", "sun.cpu.isalist" ""})

So I thought I'd print out the results, one item per line.

user=> (range 5)
(0 1 2 3 4)
user=> (for [x (range 5)] (println x))
nil 1
nil 2
nil 3
nil 4

Hm. This took a while to figure out. The trick is that for is a lazy operator, used for list comprehensions. That is, the sequence produced by for is lazy, you have to start navigating the sequence for it to fully execute. Thus, what we see above is two different streams of output, mixed together: A list: (nil nil nil nil nil) interspersed with the println calls. Here's the order of operations:

  • The Repl starts to print the sequence, starting with a "(" before the first atom
  • The first atom is evaluated, printing "0" and returning nil
  • The Repl prints the "nil"
  • The second atom is evaluated (lazily), printing "1" and returning nil
  • The Repl prints the second "nil"
  • And so on ...

That's a lesson ... in a lazily-evaluated world, even the most basics ideas have to be thrown out the window. You would never see this in Haskell, because it creates impenetrable barriers between functional, side-effect free code and any code that communicates with the outside world, even something as simple as println.

The solution? Collapse the list to a string and print that at the end:

user=> (use 'clojure.contrib.str-utils)
user=> (println (str-join "\n" (range 5)))

Baby steps. Sometimes the simplest things are tricky, but I suspect the more advanced things are easier.


Unknown said...

I've found an easier way to accomplish what I want:

(dorun (map println (range 5)))

The (dorun) function evaluates a lazy seq for the purposes of side effects, then returns null.

Unknown said...

Shawn (at Formos) points out that:

(doseq n (range 5) (println n))

is more idiomatic, as it exists just to perform side effects.

GS said...

The code in comment #2 doesn't work. You need

doseq [n (range 5)] (println n))

Dmitry Naumenko said...

Your code doesn't work as well. You need

(doseq [n (range 5)] (println n))