Thu 9 Nov 2006

If we take a look at the Haskell (.) operator:

(.) :: (a -> b) -> (e -> a) -> e -> b

and take a moment to reflect on the type of fmap

fmap :: Functor f => (a -> b) -> f a -> f b

and the unnamed Reader monad from Control.Monad.Reader

instance Functor ((->) r)

we see that fmap applied to the Reader functor rederives (.).

fmap_reader :: (a -> b) -> (e -> a) -> e -> b

So if we were willing to forgo ease of learning, and to bake in the Reader monad as a primitive, we could quite concisely redefine (.) to give it a more general signature:

module Dot where

import Control.Monad.Reader

import Prelude hiding ((.))

infixr 0 .

(.) :: Functor f => (a -> b) -> f a -> f b

(.) = fmap

In this context, existing code continues to type check. For instance,

((+2) . (*3)) 5 ==> 17

And the . above doubles as filling the role of the * map operator mentioned in Richard Bird's 1990 Calculus of Functions paper generalized to any Functor.

((+2) . (*3)) . [1..10] ==> [5,8,..32]

((+2) . (*3)) . Just 5 ==> Just 17

((+2) . (*3)) . Nothing ==> Nothing

I was able to test this with the just about every example golfed back and forth on the #haskell channel in the last 6 months.

I'm not advocating this as a practice for Haskell as it is somewhat terrifying to think of how to teach to new programmers, but I found the exercise to be enlightening.

November 9th, 2006 at 1:22 am

As an aside, the idea came from observing the fact that lambdabot’s pointfree conversion command @pl had taken to using `fmap` instead of (.) in a lot of places.

February 1st, 2007 at 2:14 am

This was added to The Other Prelude (perhaps one of the reasons it was created).

http://www.haskell.org/haskellwiki/The_Other_Prelude

Probably inspired by your irc golfings.

February 1st, 2007 at 2:15 am

PS, I don’t think it’d be too hard for noobs to learn it. When I was a noob I wondered why this didn’t work!

July 24th, 2007 at 2:58 am

I think you need doubled parens when hiding (.) in the import (the outer ones are part of the hiding syntax, the inner ones refer to the infix composition symbol in parens for lexical reasons). Both hugs and ghci seem to like it this way. (Perhaps something in your text chain snarfed the “redundant” parens?)

July 24th, 2007 at 10:05 am

Thanks. Fixed.

I honestly, just typed that off the cuff.

Thats what I get for not actually compiling it before posting ;)

March 11th, 2011 at 7:44 am

Another great post and really neat idea. I tried

((+2) . (*3)) 5

and also

((+2) . (*3)) $ 5

but this

(+2) . (*3) $ 5

complains about a missing Num instance for the literal ‘5′. It makes me wonder why the 5 is accepted in any case, as it’s not (here at least) an instance of Functor?

March 11th, 2011 at 3:02 pm

Paul:

Back when I wrote this post I gave the updated (.) the wrong precedence, it should be

infixr 9 .