In a 1988 paper called Algebraic Identities for Program Calculation, Richard Bird wrote:
Probably the most useful law about map is the fact that it distributes over functional composition: (map f) . (map g) = map (f . g)
Bird’s paper predates Haskell, and instead uses a language called Miranda, but Haskell later adopted very similar notation:
GHCi, version 6.12.1: http://www.haskell.org/ghc/ :? for help Prelude> let f x = x * x Prelude> let g x = x + 1 Prelude> ((map f) . (map g)) [1 .. 5] [4,9,16,25,36] Prelude> map (f.g) [1 .. 5] [4,9,16,25,36]
I am a sucker for nice notation, and that is some very nice notation. It got me thinking about how I might write the same thing in Clojure. Here is my first cut at a literal translation:
user> ((comp (partial map sq) (partial map inc)) (range 1 6)) (4 9 16 25 36) user> (map (comp sq inc) (range 1 6)) (4 9 16 25 36)
The Clojure code looks a little noisy by comparison, mostly due to the explicit use of partial. I came up with a couple of variations on map that clean up the presentation. The first I call mapp:
(defn mapp ([f] (partial map f)) ([f x & args] (apply map (partial f x) args )))
Here are some examples of mapp in action:
;; Using regular old map user> ((comp (partial map sq) (partial map inc)) (range 1 6)) (4 9 16 25 36) ;; We can clean that up a bit with mapp user> ((comp (mapp sq) (mapp inc)) (range 1 6)) (4 9 16 25 36) ;; Or you can do stuff like this user> (mapp * 100 (range 6)) (0 100 200 300 400 500) user> (mapp + 1/2 (range 6)) (1/2 3/2 5/2 7/2 9/2 11/2)
My other variation on map is called mapc.
(defn mapc [& args] (let [[fns xs] (partition-by fn? args) g (apply comp fns)] (if (empty? xs) (partial map g) (apply map g xs ))))
Here are some examples of mapc in action.
;; Map the (implicit) composition of two functions user> (mapc sq inc (range 1 6)) (4 9 16 25 36) ;; We can compose more than two functions user> (mapc inc sq inc (range 1 6)) (5 10 17 26 37) ;; Or use mapc like plain old map user> (mapc sq (range 1 6)) (1 4 9 16 25) ;; Or by providing only functional args, produce a function user> ((mapc sq inc) (range 1 6)) (4 9 16 25 36)
To wrap this up, note that I wanted to come close to something like
(map f) . (map g) = map (f . g)
The best I could do was
(comp (mapp f) (mapp g)) = (mapc f g)
That’s not half bad. I rather like mapp and mapc. But I genuinely wonder whether anyone else will. Play around with mapp and mapc, let me know what you think.
Update: In the comments, MarkusQ pointed out that (comp (mapp f) (mapp g)) = (mapp (comp f g)). In particular,
user> ((comp (mapp sq) (mapp inc)) (range 1 6)) (4 9 16 25 36) user> ((mapp (comp sq inc)) (range 1 6)) (4 9 16 25 36)
MarkusQ’s equation preserves symmetry by using mapp on both side of the equals sign. I think this is much prettier than the version I came up with. Keats observed that “Beauty is truth, truth beauty…”. The version Markus sent in makes me believe that mapp may well be a worthwhile abstraction.