As I promised I would the other day, I went ahead and implemented my stupid little slideshow app in both C# and Clojure. It was an interesting exercise, the more so because I’m not entirely sure what conclusions to draw. I may have more to say about this at some point in the future, but for now I thought I’d just put up the code and share some general observations. So, you can find the C# version here, and the Clojure version here.
The app is pretty simple. It starts a low-priority thread that lazily populates a list of filenames that are descendants of a particular directory. Meanwhile, a timer procedure randomly selects one of the filenames out of the ones that have been found so far and invalidates the screen. The repaint simply draws the image centered on a fullscreen window with a black background. If no images have been found yet, then it prints “Working…” on the screen instead.
The Clojure version isn’t really an app, because I wrote all this code really quickly and didn’t bother giving it a main() function, but other than that they’re about as similar as I could make them, given that one is running against Swing on the JVM and the other against Windows Forms on the CLR. I suppose I could have written the Clojure one against Windows Forms as well using ClojureCLR, but honestly it was simply more interesting to me to use Swing, as I’m not familiar with it. And this is anything but a scientific experiment.
For the C# version, I eschewed the Forms designer to keep it closer to the spirit of the Clojure one. And for something like this, where I’m just blasting raw pixels at the screen, I think that was a reasonable choice.
I wrote the Clojure version first. Perhaps that was a bad choice, since it meant I spent a lot more time on the Clojure one, since I had to figure out all the little details, like how to center an image in a region. It certainly made the C# version a lot easier to write, since I was pretty much just translating. But the C# version probably would have been easier for to write in any event, since I’ve been writing C# professionally for almost 10 years, and Clojure in my spare time for less than two.
One thing that surprised me a bit is that the Clojure version turned out to be not much shorter than the C# version. Indeed, if I had put closing braces on the same line as the statement they close (which is like the Clojure style I use), I’d bet they’re about the same. I had expected it to be quite a bit more compact, since, as I’ve said before, my experience with Clojure has been that it generally lets you be more concise. In this case, I suspect it has to do with the fact that I had a lot of Swing-related code, which doesn’t lend itself strongly to the sorts of concision that Clojure supports. Or it could be the fact that I’m a Clojure n00b, and just don’t know how to structure the code to hit a good balance of terseness with readability. Certainly, I could make it a bit shorter by inlining calls to things like make-frame. Other recommendations as to how better to write either version would be more than welcome.
Both versions of the code are pretty rough, as I wrote them essentially while I was waiting for stuff to install on a VM. I’d definitely clean them up more if I were going to do anything with them, but I figured it was better to get it up here than to wait potentially forever to get it polished. Really, I’m just trying to point out that not all my code looks this crappy. :)
You’ll note that in the Clojure version, I use an atom to store the list of filenames. That’s not because I’m taking advantage of Clojure’s awesome concurrency support, it’s because it was a convenient thing to do. Note that the C# version doesn’t bother with synchronization at all, since I’ve only got two threads, and one is only reading, and one is only appending. If I wanted to get fancier with the threads (e.g. have another thread doing look-ahead loading of the next image), then I might have to start doing synchronization. I’d be in good shape on the Clojure side then, since not much would have to change, and the fact that Clojure values are immutable really helps. I don’t think the C# version wouldn’t need too much work, but I’d have to think about it more to be sure, and it would depend on exactly what I was trying to do.
It’s apropos of nothing, but it made me smile that the C# version required PInvoke to get a fullscreen window, and the Swing one just supported it out-of-the-box.
Being familiar with Visual Studio was a definite benefit when working with the C# code. I’m not very facile with the debugging capabilities of Emacs/SLIME, which is what I use to develop Clojure, and when I had problems it took me a little longer to figure them out. That said, having the REPL was really awesome, and it really makes me hate the Immediate Window in Visual Studio by comparison. It would be interesting to try some of the other Clojure IDEs to see how they stack up.
Neither version of the code has much in the way of comments. Sorry about that. :)
Anyway, that’s my pile of unstructured thoughts. Hope it’s of interest, if not value, to at least some of you. And as I said, further discussion, rewrites, comments, and criticism are wholly welcome.
Hey,
ReplyDeleteFirst of all I think you did a very very job stepping into Clojure. Though in a few places it shows that you're used to C#.
For instance do you define the sequence using a loop with side-effects, it takes an extra line to implement it in a purely functional lazy sequence, but I think its worth it. Secondly I see you add a lot of named variables which in more than a few cases isn't necessary or helpful.
I rewrote your code to address those two issues, which brought the code-size down to about 95 lines. Then it dawned on me, that you don't need another mutable construct to determine a 'currentimage', since all you need is a randomly selected index. In addition I don't see why exactly you'd want a timer, since a future would have the exact same effect.
So with the above in mind I added a macro to simplify some of the repetive Java interop and that brought the code down to 85 lines without loosing anything (I hope), so we're coming up on 50% of the C# code. You can read the code here:
http://gist.github.com/335511
Hope to see more from you in the near future,
Lau Jensen
Very cool, Lau. Thanks for taking the time to have a look. I really like the props macro, and the rest of the cleanup you did makes sense to me, too. The idea of using a future with sleep is also nice - as you say, the timer introduces unnecessary complexity.
ReplyDeleteYour use of when-let is an obvious "duh" now that I see it - I suppose I should always be on the lookout for an "if" where one of the clauses evaluates to nil. As you say, my C# background is showing here - like others I've found that it's not the switch to Clojure so much as the switch to functional that's difficult.
An interesting experiment for me would be to see if some of these changes would survive the translation to C#.
The named variables were generally there as leftovers from debugging - pasted in from intermediate steps in the REPL. Along the same lines, the C# version actually squishes compute-bounds and fit-to together, as I've got a repeated calculation in there. That would make it even shorter and (hopefully) clearer.
All in all, it has been an interesting experiment. Thanks again for helping out with it!
One question that springs to mind is whether Clojure's gensym support (the # suffix) wouldn't make the macro a little easier to read. But perhaps I'm missing something.
ReplyDeleteHey Craig,
ReplyDeleteNp - Fun task.
Regarding gensym then you can simply try changing to the # format and run (macroexpand-1 '(props img .getHeight .getWidth)) to see the results. You'll notice that a different gensym is generated for each iteration of the for-loop. This is intentional, though not optimal for a job such as this.
If you're interested in how the macro came to be, you can check out this thread, as a Microsoft guy I suppose you'll like it :)
http://www.bestinclass.dk/index.php/2010/02/my-tribute-to-steve-ballmer/
Interesting Finds: March 18, 2010
ReplyDelete@Lau: ha, yes, I now remember seeing your ASCII art post last month. Good stuff!
ReplyDelete