Writing Files in 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.