Generics in Clojure

Clojure introduced protocols and multimethods as a way to achieve polymorphism and generic-like behavior. While Clojure doesn’t have built-in generics like some statically-typed languages, we can achieve similar functionality using these features.

Let’s start with an example similar to the SlicesIndex function:

(defn index-of
  "Returns the index of the first occurrence of v in coll, or -1 if not found."
  [coll v]
  (let [i (.indexOf coll v)]
    (if (= i -1) -1 i)))

(println "index of zoo:" (index-of ["foo" "bar" "zoo"] "zoo"))

In Clojure, we don’t need to specify type parameters as it’s a dynamically-typed language. The index-of function works with any collection that supports the .indexOf method.

Now, let’s create a generic-like linked list:

(defprotocol IList
  (push [this v])
  (all-elements [this]))

(deftype List [^:volatile-mutable head]
  IList
  (push [this v]
    (set! head (cons v head)))
  (all-elements [this]
    (reverse head)))

(defn create-list []
  (List. nil))

(let [lst (create-list)]
  (push lst 10)
  (push lst 13)
  (push lst 23)
  (println "list:" (all-elements lst)))

In this example, we define a protocol IList that specifies the methods our list should implement. The List type implements this protocol. We use a protocol here to achieve a form of polymorphism that’s similar to generics in other languages.

The push method adds an element to the front of the list, and all-elements returns all elements in the list.

To use our list:

(defn -main []
  (println "index of zoo:" (index-of ["foo" "bar" "zoo"] "zoo"))
  
  (let [lst (create-list)]
    (push lst 10)
    (push lst 13)
    (push lst 23)
    (println "list:" (all-elements lst))))

To run the program, you would typically put this code in a file (e.g., generics.clj) and run it using the Clojure command-line tool:

$ clj generics.clj
index of zoo: 2
list: (10 13 23)

In Clojure, we achieve generic-like behavior through protocols and polymorphism. While it’s not exactly the same as generics in statically-typed languages, it allows us to write flexible, reusable code that can work with different types.