Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Saving+reading sorted maps to a file in Clojure

Tags:

clojure

I'm saving a nested map of data to disk via spit. I want some of the maps inside my map to be sorted, and to stay sorted when I slurp the map back into my program. Sorted maps don't have a unique literal representation, so when I spit the map-of-maps onto disk, the sorted maps and the unsorted maps are represented the same, and #(read-string (slurp %))ing the data makes every map the usual unsorted type. Here's a toy example illustrating the problem:

(def sorted-thing (sorted-map :c 3 :e 5 :a 1))
;= #'user/sorted-thing
(spit "disk" sorted-thing)
;= nil
(def read-thing (read-string (slurp "disk")))
;= #'user/read-thing

(assoc sorted-thing :b 2)
;= {:a 1, :b 2, :c 3, :e 5}
(assoc read-thing :b 2)
;= {:b 2, :a 1, :c 3, :e 5}

Is there some way to read the maps in as sorted in the first place, rather than converting them to sorted maps after reading? Or is this a sign that I should be using some kind of real database?

like image 833
James Avatar asked Jun 27 '13 15:06

James


2 Answers

The *print-dup* dynamically rebindable Var is meant to support this use case:

(binding [*print-dup* true]
  (prn (sorted-map :foo 1)))
; #=(clojure.lang.PersistentTreeMap/create {:foo 1})

The commented out line is what gets printed.

It so happens that it also affects str when applied to Clojure data structures, and therefore also spit, so if you do

 (binding [*print-dup* true]
   (spit "foo.txt" (sorted-map :foo 1)))

the map representation written to foo.txt will be the one displayed above.

Admittedly, I'm not 100% sure whether this is documented somewhere; if you feel uneasy about this, you could always spit the result of using pr-str with *print-dup* bound to true:

(binding [*print-dup* true]
  (pr-str (sorted-map :foo 1)))
;= "#=(clojure.lang.PersistentTreeMap/create {:foo 1})"

(This time the last line is the value returned rather than printed output.)

Clearly you'll have to have *read-eval* bound to true to be able to read back these literals. That's fine though, it's exactly the purpose it's meant to serve (reading code from trusted sources).

like image 199
Michał Marczyk Avatar answered Oct 17 '22 14:10

Michał Marczyk


I don't think its necessarily a sign that you should be using a database, but I do think its a sign that you shouldn't be using spit. When you write your sorted maps to disk, don't use the map literal syntax. If you write it out in the following format, read-string will work:

(def sorted-thing (eval (read-string "(sorted-map :c 3 :e 5 :a 1)")))
(assoc sorted-thing :b 2)
;= {:a 1, :b 2, :c 3, :e 5}
like image 26
dbyrne Avatar answered Oct 17 '22 13:10

dbyrne