Writing Files in Clojure

November 16, 2009. Filed under clojure

While getting started looking into Clojure, it took me a bit of investigation to figure out reading from files, and understanding writing out files required a bit more investigation of its own. Outlined below are a few recipes for writing files in Clojure, first using clojure.contrib.duck-streams, and then relying on java.io.BufferedWriter.

clojure.contrib.duck-streams.spit

duck-stream.spit is the imagine counterpart to the Clojure core's slurp function. For simple operations with small files, spit is probably your best bet.

user> (require 'clojure.contrib.duck-streams)
nil
user> (clojure.contrib.duck-streams/spit "output.txt" "test")
nil

Which will create the output.txt file with the contents "test". There is also the append-spit variant, which appends the string to the file.

user> (require 'clojure.contrib.duck-streams)
nil
user> (clojure.contrib.duck-streams/append-spit "output.txt" "test")
nil

These are really quite helpful functions, but keep in mind you'll need to install clojure.contrib before you can take advantage of them.

clojure.contrib.duck-streams.write-lines

For streaming writing to files, once again duck-streams provides the simplest approach via write-lines.

user> (require 'clojure.contrib.duck-streams)
nil
user> (clojure.contrib.duck-streams/write-lines "output.txt"
                                                (list 1 2 3 4))
nil

Unfortunately, not everyone will immediately install clojure-contrib, so let's take a look at writing to files without duck-streams.

java.io.BufferedWriter

When the Clojure core and contributed libraries let us down (or if we don't want to install the contributed library, in this case) the next place to turn is The Java Way: in this case java.io.BufferedWriter.

At its simplest, java.io.BufferedWriter can help us duplicate the duck-streams.spit functionality.

(ns tokenize
  (:import (java.io BufferedWriter FileWriter)))

(defn spit2 [file-name data]
  (with-open [wtr (BufferedWriter. (FileWriter.	file-name))]
    (.write wtr	data)))

(spit2 "output.txt" "this is some data...\n")

However, spit2 shares the same flaws as slurp, requiring the entire file to be held in memory rather than acting on streams of data. We can use BufferedWriter to write a more efficient approach as well.

(ns tokenize
  (:import (java.io BufferedWriter FileWriter)))

(defn write-lines2 [file-name lines]
  (with-open [wtr (BufferedWriter. (FileWriter. file-name))]
    (doseq [line lines] (.write	wtr line))))

(write-lines2 "output2.txt" (list "a\n" "b" "c" "d\n" "e\n"))

One of these four techniques (duck-streams.spit, duck-streams.write-lines, spit2 or write-lines2) should be enough for most of your file writing needs. The next scenario to consider is a system which lazily reads from a file, performs some operation, and then lazily writes the output to a file. Hopefully that'll be ready in a day or two.