A clj snippet #3

Today's snippet comes from a small clojure contrib library called algo.generic. The library helps to extend standard clojure.core functions to your own types. These extensions are implemented via multimethods.

{:deps
 {compliment {:mvn/version "0.3.10"}
  org.clojure/algo.generic {:mvn/version "0.1.3"}}}  
deps.edn
Extensible Data Notation
(use '[clojure.algo.generic.functor])
(fmap inc {:a 1 :b 2 :c 3})
0.1s
unrepl (Clojure)
Map {:a: 2, :b: 3, :c: 4}

fmap behaves like map but returns the result as the same type as the input structure. As you can see above, for maps the mapping function is applied to the values. fmap unlike map only works on one collection. Let's create a version called fmap* that works on more than one collection and which asserts that every collection has the same type.

(defmulti fmap* (fn [f & colls]
                  (assert (apply = (map type colls)))
                  (type (first colls))))
(defmethod fmap* clojure.lang.PersistentVector
  [f & colls]
  (into [] (apply map f colls)))
(fmap* + [1 2 3] [1 2 3 4] [1 2 3 4 5])
0.1s
unrepl (Clojure)
Vector(3) [3, 6, 9]

Now, lets put fmap* to some use and extend some standard operators.

(require '[clojure.algo.generic.arithmetic :as generic])
;; elementwise plus 
(defmethod generic/+
  [clojure.lang.PersistentVector clojure.lang.PersistentVector]
  [x y]
  (fmap* + x y))
(defmethod generic/+
  [java.lang.Long clojure.lang.PersistentVector]
  [x y]
  (fmap (partial + x) y))
;; dot product
(defmethod generic/*
  [clojure.lang.PersistentVector clojure.lang.PersistentVector]
  [x y]
  (fmap* * x y))
0.1s
unrepl (Clojure)
Vector(4) [clojure.lang.MultiFn, "0x966fc42", "clojure.lang.MultiFn", Map]
(use 'clojure.algo.generic.arithmetic)
(+ 42 [1 2 3] (* [1 2 3 4] [1 2 3 4 5]))
0.0s
unrepl (Clojure)
Vector(3) [44, 48, 54]

The nice thing about alog.generic is that it automatically gives you variadic versions if you implemented the binary version of an operator because the default method for the nary version of + looks as follows:

(defmethod + nary-type ;; a dispatch value defined by algo.generic 
  [x y & more]
  (if more
    (recur (+ x y) (first more) (next more))
    (+ x y)))
0.5s
unrepl (Clojure)

As you realize now fmap* was actually not really needed to implement the extension for the arithmetic operators, we could just as easy done something like

(into [] (map + [1 2 3] [4 5 6]))
0.1s
unrepl (Clojure)
Vector(3) [5, 7, 9]

in the genereic/+.

Still, with fmap* you are able to quickly map functions or combine them if you haven't yet written a generic extension for them

(defn exp [x n]
  (reduce * (repeat n x)))
(fmap* (fn [x y z] (exp x (exp y z))) [1 2 3] [1 2 3] [1 2 3])
0.1s
unrepl (Clojure)
Vector(3) [1, 16, 7625597484987]
Runtimes (1)