Of Clojure Lists And Vectors

I converted a minute Scala assignment to Clojure to experience the differences in solving the assignment. There were possibilities that I solved the issues the Scala way and not knowing there are better Clojure methods to resolve them. I tried to be as objective as possible while I was completing the quest.

Long story short, unlike Scala and Java, Clojure does not do classes, obviously, and uses the only four (4) fundamental structures lists, maps, vectors and sets to build bigger and more complex structures, e.g. maps of maps of lists and sets, to represent artefacts from the real world.

Second, I want to highlight these confusing commands I worked with during my assignment. The results from the same command were different depending on the type of structure used. Before we go into the example to show you the behaviour I am referring to, let me give you a short background on

  1. list e.g. ‘(1 2 3) or (list 1 2 3); in Clojure, you must declare your list with a ‘ (single quote) otherwise it will treat the first element as a command instead of an element.
  2. vector e.g. [1 2 3] or (vector 1 2 3)
  3. cons returns a new collection of a new element and the given collection
  4. conj returns a new collection of a new element and the given collection
  5. into returns a new collection that combines two given collections, think “union”

() in Clojure is a linked list (LinkedList in Java) whereas [], a vector, is an array (ArrayList in Java). There are subtle differences between them and I find them bit annoying using them and easy to make mistakes. Lots of practices required here.

Let us go to the examples, the result for each command is preceded with “=>”.

;; cons and conj with vector
(conj [1 2 3] 4)
; => [1 2 3 4]
 ​
(conj 4 [1 2 3]) 
; => java.lang.ClassCastException: java.lang.Long cannot be cast to clojure.lang.IPersistentCollection …
 ​
(cons [1 2 3] 4)
; => java.lang.IllegalArgumentException: Don't know how to create ISeq from: java.lang.Long …
 ​
(cons 4 [1 2 3])
; => (4 1 2 3)
 ​
;; cons and conj with list
(conj '(1 2 3) 4) 
; => (4 1 2 3)
 ​
(conj 4 '(1 2 3))
; => java.lang.ClassCastException: java.lang.Long cannot be cast to clojure.lang.IPersistentCollection …
 ​
(cons '(1 2 3) 4)
; => java.lang.IllegalArgumentException: Don't know how to create ISeq from: java.lang.Long …
 ​
(cons 4 '(1 2 3))
; => (4 1 2 3)
 ​
(into '(4) '(1 2 3))
; => (3 2 1 4)
 ​
(into '(1 2 3) '(4))
; => (4 1 2 3)
 ​
(into [4] [1 2 3])
; => [4 1 2 3]
 ​
(into [1 2 3] [4])
; => [1 2 3 4]
 ​
(rest '(1 2 3 4))
; => (2 3 4)
 ​
(rest [1 2 3 4])
; => (2 3 4)

​Depending on the structure (list or vector), (cons …) or (conj …) operates on, the new element could be added as the first or the last element in the new collection. This could pose a serious problem if the position of the new element added to the collection matters.

Finally, if you want to combine two vectors together where the elements in second vector are added after the last element in the first vector and the first element in the first vector is removed. On top of this, the order of the elements is utter important. We can achieve this using the command (into …) and (rest …) i.e.

(into (rest [1 2 3 4]) [5])
; =>  (5 2 3 4)

However, the result returned is (5 2 3 4) and not the expected result of [2 3 4 5]. What has happened?

(rest …) will return a list of elements minus the first element regardless what is the given collection type it operates on. Subsequently, when the result is passed to (into …) as in (into (rest [1 2 3 4]) [5]),  the result is (5 2 3 4) instead of the expected [2 3 4 5]. Please refer to the examples above for clarity on (into …) operates on list and vector. (rest …) drops the first element and silently converts the vector to a list before passing the result to (into …). The remedy is to convert the list to vector before passing to (into …) i.e. (into (vec (rest [1 2 3 4])) [5]) and you have the expected result.

(into (vec (rest [1 2 3 4])) [5])
; => [2 3 4 5]

This is a hard to find bug. There was no error reported or exception thrown because Clojure is a dynamic typing language. So, coming from a statically typed language world, this is something really unexpected. I have no other recommendation other than writing sufficient test cases to cover all the outcome to nail this bug.

Advertisements

One thought on “Of Clojure Lists And Vectors

  1. The reason this is happening is that Clojure uses the destination types append operation. For a vector that’s adding on the right hand side, for a list it’s adding on the left hand side. The two types also differ in their efficiency when adding items: vectors are efficient at adding on the right, and inefficient for adding at the start.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s