Search code examples
typesclojurefunctional-programmingcontainers

vectors vs lists with immutable data in Clojure


I have written the same function twice, each using a list and a vector respectively. The function finds an element and returns the next one in the collection and wraps around if the found element is at the end. nil is returned if the element cannot be found.

List version

(def syms '(\< \^ \> \v))

(defn next-elem- [coll elem]
  (loop [coll-rest coll]
    (cond
      (empty? coll-rest) nil
      (= (first coll-rest) elem) (nth coll-rest 1 (first coll))
      :else (recur (rest coll-rest)))))

(defn rotate-left [sym]
  (next-elem- syms sym))

Vector version

(def syms [\< \^ \> \v])

(defn next-index- [coll i]
  (let [elem-count (count coll)]
    (cond
      (or (< i 0) (> i elem-count)) -1
      (= i (dec elem-count)) 0
      :else (inc i))))

(defn rotate-left [sym]
  (let [i (.indexOf syms sym)]
    (get syms (next-index- syms i) nil)))

Tests

(assert (= \< (rotate-left \v)))
(assert (= nil (rotate-left \o)))

Which version is better? I have read that lists are generally preferred in functional programming and at least in F# vectors (there arrays) are mutable, which is not what I need. Handling indices also feels awkward but is easier to wrap your head around as a non-functional programmer.

PS: This is one of the first functional programs I have written, so it might not be optimal. PPS: Am I using the backslashes correctly or should I use something else in its place?


Solution

  • First version of next-elem- is better because it would work with any sequence. Second version relies on sequence to have an efficient index access, which is really easy to accidentally fail and get a low-performing code.

    An advice: change rest to next. Rest is sort of deprecated, next is better in all cases.

    Also avoid using lists. They are very specific data structure, rarely needed. Whenever you need a linear sequence, consider vector first.

    A functional version of your code:

    (defn next-elem- [coll elem]
      (->> (concat coll [(first coll)])
           (drop-while #(not= % elem))
           (second)))