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"}}}
(use [clojure.algo.generic.functor])
(fmap inc {:a 1 :b 2 :c 3})
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])
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))
(use clojure.algo.generic.arithmetic)
(+ 42 [1 2 3] (* [1 2 3 4] [1 2 3 4 5]))
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)))
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]))
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])