Showing posts with label Clojure. Show all posts
Showing posts with label Clojure. Show all posts
Wednesday, July 6, 2011
A strange behavior in Clojure
I see this behavior in Clojure 1.2.1:
user=> (type '(1 2))
clojure.lang.PersistentList
user=> (type `(1 2)) ;; notice syntax-quote
clojure.lang.Cons
user=> (type '(1))
clojure.lang.PersistentList
user=> (type `(1))
clojure.lang.PersistentList
I expected `(1) to be a Cons just like `(1 2) is.
I also tried:
user=> (type (cons 1 nil))
clojure.lang.PersistentList
user=> (type (cons 1 `()))
clojure.lang.Cons
user=> (type (cons 1 '()))
clojure.lang.Cons
user=> (type (cons 1 []))
clojure.lang.Cons
So what is the reason for `(1) and (cons 1 nil) to be PersistentLists?
It turns out that you shouldn't program against those exact types but against the seq abstraction.
However, I think I can take a guess at the reason. The Clojure forms that produce a PersistentList ultimately call RT.java, specifically the cons(Object x, Object coll) method. It begins with a pretty odd check: if(coll == null) return new PersistentList(x), after which it creates a Cons object if that check doesn't pass. If you look at earlier versions of the code, you can find this:
static public IPersistentCollection cons(Object x, IPersistentCollection y) {
if(y == null)
return new PersistentList(x);
return y.cons(x);
}
So in an earlier version of the function, the call was dispatched to the cons method of the second argument, so the case when the second argument was null (i.e. nil in Clojure) needed special handling. Later versions don't do that dispatching (or actually do it but in a different way, presumably to support a larger variety of collection types), but the check has been retained since it does not break any correctly written code.
Wednesday, February 9, 2011
Partioning a Number Sequence in Clojure
I have the following input:
(def nums [123456789012 123456789012])
I'd like the following output:
[[1234 5678 9012] [1234 5678 9012]]
Note both of these sequence contain numbers not strings...
I figured this would be really simple by doing the following:
Convert each entry into a String
Partition each string by 4
Convert each partition back into an integer
Attempt 1:
(defn split-nums [nums factor]
(map
#(map
(fn [x] (Integer/valueOf (str x)))
(partition factor (str %)))
nums))
(println (split-nums nums, 4))
When I run this I get the following error:
Caused by: java.lang.NumberFormatException: For input string: "clojure.lang.LazySeq@4834333c"
Which tells me I am dealing with a lazy sequence that I need to force evaluation on but when I try to (str (doall x)) I get the same result.
Attempt 2:
A version with / and mod. This also fixes leading zeros problem.
(defn int-partition [num size]
(let [f (int (Math/pow 10 size))]
(loop [n num l ()]
(if (zero? n)
(vec l)
(recur (int (/ n f)) (conj l (mod n f)))))))
(defn split-nums [nums factor] (vec (map #(int-partition % factor) nums)))
Attempt 3:
(defn split-nums [nums factor]
(map #(map (fn [x] (Integer/valueOf (apply str x))) ; apply str
(partition factor (str %)))
nums))
(str (lazy-seq [1])) ; "clojure.lang.LazySeq@20"
(apply str (lazy-seq [1])) ; "1"
I'd probably write it to accept one number, then use map, instead of taking a coll.
(defn split-number [n factor]
(->> (str n)
(partition-all factor) ;; or partition
(map (partial apply str))
(map #(Integer/valueOf %))))
(map #(split-number % 4) [12345678 12345678]) ;; => ((1234 5678) (1234 5678))
Bingo!