Archive

Archive for May, 2011

Nice Weather for Some Clojure (UIs)

May 24th, 2011 9 comments

Summary: I tried build something slightly non-trivial with Seesaw and learned some things.


And so it came to pass that it seemed like I needed to start dog-fooding Seesaw a bit. I’ve been steadily adding features and building tiny toy apps to demonstrate them. Others (and others) have apparently found it useful, but I haven’t tried anything myself.

So, I decided to write Gaidica, a little app that displays weather info for user-selected cities. The whole process wasn’t terrible, but shows that there’s work to do. I’d guess about 4 hours of work including fixing Seesaw bugs as I hit them and at least an hour puzzling over Clojure XML stuff. Here’s a screenshot of the forecasts:

The code is all available on github.

Intro

Just to make sure we’re on the same page, Seesaw is an API/DSL for building user interfaces in Clojure. It happens to be built on Swing as you’ll see below, but please don’t judge it too harshly. I’m working hard to make it so you never need to know that Swing’s even there.

The Data

I decided to use Weather Underground’s XML API for the data. It provides the data in a simple format and as a bonus, includes icons for weather conditions, which saved me some work. Additionally, I got to do some XML wrangling in Clojure. Let’s just say that that deserves a blog post of its own.

In any case, I just point Clojure’s (xml/parse) function at the URL and get back some results. All this is done in a background thread using (future) to keep the UI responsive.

The Good

I mostly want to focus on the bad in this post, but despite any difficulties I had, building this was more enjoyable in Clojure+Seesaw than the equivalent Clojure+Swing or raw Java code. The lack of (proxy) and (reify) usage is testament to that.

Seesaw’s table support, seen in the webcam tab below, really paid off. Compared to the equivalent JTable-wrangling you’d have to do, it was downright dreamy.

Here’s the code to create that table:

(defn make-webcam-table [] 
  (table 
    :id :webcam-table 
    :model 
      [:columns 
        [{:key :handle :text "Name" } 
         :lat :lon 
         {:key :updated :text "Last Updated"}
         {:key :image :text "Image"}]]))

The Bad

I learned a lot during this exercise. One thing I noticed is that Swing is a pain in the ass. I think when you’re in the depths of a Java-based Swing app, you don’t even notice it because you’re already so mired in Java verbosity. It’s a form of Stockholm Syndrome I guess.

Most of the pain centered around, you guessed it, layout. I wanted to do it with the vanilla Swing layouts provided by Seesaw (border-panel, vertical-panel, etc), but after fiddling with component alignments and lame resize problems, I eventually surrendered and ran back to MigLayout. It’s powerful and pretty easy to use, but it’s yet another little language to learn. Luckily Seesaw already has Mig support.

Otherwise, most of the other issues I had were just missing functionality. Swing methods not yet wrapped by Seesaw. The nice thing is that since Seesaw’s a fairly thin wrapper, it’s easy to drop down to raw Swing when needed. Some random functionality that I’m going to address as a result:

  • Manipulation of the widget hierarchy needs to be seamless. This has historically been a problem in Swing (invalidate(); revalidate(); repaint()!!)
  • Widget styling. Sprinkling fonts and colors throughout the code is a hassle. The main question is whether I just add convenience options for this, or go whole hog and try for a CSS-style styling.
  • Saner defaults for “vanilla” layouts. I think I can tune some of the defaults and add some dials to make it less frustrating. It still really bugs me that I had to use Mig for something so simple.

Conclusion

From the perspective of a potential Seesaw user, already suspicious of Swing, all of this is a problem. I’ll take it as a challenge to continue making Seesaw a buffer between them and the occasional craziness of Swing. Feedback welcome!

Categories: clojure, seesaw, swing Tags: , ,

The Curious Case of Clojure’s (case)

May 14th, 2011 1 comment

Clojure has a (case) macro which cost me a little time last night because I didn’t read the documentation carefully enough. Consider this REPL session:

user=> (def x 1)
#'user/x
user=> (def y 2)
#'user/y
user=> (case 1,   x "GOT x",    y "GOT y",    "NOTHIN'")
"NOTHIN'"
user=> (case x,   x "GOT x",    y "GOT y",    "NOTHIN'")
"NOTHIN'"

That seems a little odd. Two “constants” used as test expressions, but unexpected results. How about this:

user=> (case 'x,    x "GOT x",    y "GOT y",   "NOTHIN'")
"GOT x"

Interesting. It’s almost like (case) doesn’t evaluate the test expressions. I’ll read the docs a little closer:

The test-constants are not evaluated. They must be compile-time
literals, and need not be quoted. If the expression is equal to a …

Ah. Mystery solved. x and y in my case are treated as raw symbols! For me, this is a surprisingly un-dynamic behavior for Clojure. I understand it’s for constant-time dispatch, but still.

For what it’s worth, the real example where this came up makes it less obvious:

(case result
   JFileChooser/APPROVE_OPTION   ...
   JFileChooser/CANCEL_OPTION    ...
   ...)

Maybe this will help someone else be less dense than me. The correct solution, I think, is to use condp or cond:

user=> (condp = 1,    x "GOT X",    y "GOT Y",   "NOTHIN")
"GOT X"

Cheers.

Categories: clojure Tags:

Java Concurrency Pitfalls (in Scala) Answers

May 10th, 2011 3 comments

Recently, Cay Horstmann posted a dozen Java concurrency pitfalls ported to Scala for extra pitfalliness. I had fun figuring them out. Here’s what I came up with.

I won’t copy all the code here, just provide my answers.

  1. The var stop isn’t synchronized in any way (synchronized, AtomicBoolean, etc) so there’s a chance that when it’s set to false, the change won’t be seen in the thread.
  2. Here, stop is an AtomicBoolean, fixing the issue in problem #1. That’s good. However, if doSomething() throws an exception done.put("DONE") will never execute and done.take() will wait forever.
  3. Here, Thread.run() is called rather than Thread.start(). The so-called “BackgroundTask” will actually run in the “foreground”
  4. ConcurrentHashMap.keySet().toArray will give a “weakly consistent” snapshot of the keyset at the time of the call. By the time it returns, the keys in the map may have changed completely.
  5. Oops. A string literal is used for the lock meaning that all instances of Stack will most likely share the same lock.
  6. Oops. A new “lock” is created every time push() is called, which is just as good as no lock.
  7. Here values is mercifully synchronized, but the var size isn’t. That is, if size is used in any other methods it may not be synchronized correctly.
  8. A do/while is used for the condition variable rather than just while. If cond is in a signaled state initially, the condition that size is zero may not be checked.
  9. If out.close() throws an exception, the myLock.unlock() will not be executed.
  10. Here, a string is being added to a blocking queue within a UI event handler. If the queue is full, queue.put() will block causing the UI to become unresponsive.
  11. I’m not totally sure with this one. I can think of a few things that could go wrong. First, if a listener is added or removed while fireListeners() is executing, you could get a ConcurrentModificationException. I think so anyway. I wasn’t able to find any indication of how ArrayBuffer iteration handles this. Second, since the listeners are notified with the lock held, you’ll almost certainly get a deadlock eventually, usually a lock inversion with some other thread that’s calling SwingUtilities.invokeAndWait().
  12. Ridiculously, SimpleDateFormat is NOT THREADSAFE! So using the same formatter from multiple threads is a recipe for sadness.

I wonder what I missed.

Cheers!