August 16, 2012

An I/O pitfall in Clojure (or, RandomAccessFile says "invalid handle")

I had this problem today. I was attempting to use RandomAccessFile and kept getting errors trying to write to it.  Things like, "Write error [Thrown class java.io.IOException]" 

I eventually figured out the problem. Here is an example.

1 2 3 4 5 6 7 8
 
; Throws an error
(with-open [f (RandomAccessFile. "test_file.tmp", "rws")]
(map #(.writeInt f %1) (range 1000)))
 
; Runs happily
(with-open [f (RandomAccessFile. "test_file.tmp", "rws")]
(dorun (map #(.writeInt f %1) (range 1000))))

It's easy to forget, but map (and for and repeatedly and others) return lazy sequences. A side effect of this is that their items are not guaranteed to be executed in the order the code is written.

The Magic Word: "dorun"

Dorun, doall and doseq are your friends in clojure. They all do about the same thing: force the execution of their contents, probably some sort of lazy sequence, before continuing.

You should never use map in a with-open block without applying one of these. It seems that the code in the map is not guaranteed to be executed before the with-open closes the file, leaving you attempt to write to an empty handle.

I hope that helps you, if you stumbled here searching for some of those words.