September 9, 2010

Type chasing 'fmap'

I recently tried to explain Haskell's fmap type signature to someone, and discovered that it was a lot easier than the first time I tried to understand (or explain) some of the Linq type signatures. The issue was trying to figure out what "things" can be passed to fmap, and what can be expected back. Here is a sample ghci session that I used to explain just that.
-- let's give ourselves a nice prompt
Prelude> :set prompt "ghci> "

-- ok, now, what's the type signature for fmap?
ghci> :type fmap
fmap :: (Functor f) => (a -> b) -> f a -> f b

-- Aha! So we see that it takes a function, and a Functor.  I think we
-- know how to create, or how to recognize a function (a -> b).
-- What about Functors, what can we plug there?
ghci> :info Functor
class Functor f where
  fmap :: (a -> b) -> f a -> f b
  (GHC.Base.<$) :: a -> f b -> f a
        -- Defined in GHC.Base
instance Functor Maybe -- Defined in Data.Maybe
instance Functor [] -- Defined in GHC.Base
instance Functor IO -- Defined in GHC.Base

-- Interesting. A Functor is not a data type, but a typeclass.
-- An interface, for those of us with OO background.
-- Furthermore, we see that the following types implement the
-- Functor typeclass: Maybe, [], and IO.
-- So it would seem that fmap allows us to apply a function to a value
-- wrapped inside a Functor.  Let's find something that we can use
-- test this.
ghci> :module +Data.Time
ghci> :type getCurrentTime
getCurrentTime :: IO UTCTime

-- So, the getCurrentTime function returns to us a UTCTime wrapped
-- in the IO monad.  Furthermore, we have seen that IO implements Functor,
-- so IO UTCTime could fit the bill for the "f a" that we have a 
-- need for in fmap.  Now we need a function that takes
-- a UTCTime and returs something.  Hmmm... Let's turn to hoogle
-- (at http://www.haskell.org/hoogle/) and search for functions that have
-- (UTCTime -> a) type signature.  Ah! There is one, let's check it out:
ghci> :type utctDay
utctDay :: UTCTime -> Day

-- This UTCTime -> Day also fits the (a -> b) that we need for fmap!
-- So, recapping:
-- for fmap :: (Functor f) => (a -> b) -> f a -> f b
-- we have:
-- utctDay :: UTCTime -> Day for the place of (a -> b)
-- getCurrentTime :: IO UTCTime for the place of f a
-- ... and according to fmap's type, we should expect IO Day as a result.
-- Let's check:
ghci> :t fmap utctDay getCurrentTime
fmap utctDay getCurrentTime :: IO Day

-- Great!  All the ends match :)  Let's see what this does:
ghci> fmap utctDay getCurrentTime
2010-09-09
That was pretty easy, wasn't it?

No comments: