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.

Posts
ML predates Miranda and had nearly identical syntax to Haskell. I think I even prefer o over . since it looks more like the mathematical notation. Looks even better with syntax highlighting :)
val l = [1,2,3,4,5]
fun f x = x * x
fun g x = x + 1
((map f) o (map g)) l (* [4,9,16,25,36] *)
map (f o g) l (* [4,9,16,25,36] *)
May 23, 2011 @ 9:28 am
Interesting. Part of the beauty of the original (to me) is its symmetry. For that reason, I’d favour something like:
(comp (mapp f) (mapp g)) = (mapp (comp f g))
…where the same operations are used on both sides.
– MarkusQ
May 23, 2011 @ 9:40 am
Markus,
I like that a lot. Your point about symmetry is spot on. Thanks.
Brian,
As you point out, both ML and Haskell’s notation were borrowed from mathematics, and a good notation is a thing of beauty. I like this quote from A. N. Whitehead:
BTW, see you tomorrow night at Trifunc.
May 23, 2011 @ 1:31 pm