[reactive] To fmap fmap or not?
bugfact at gmail.com
Thu Nov 20 17:21:32 EST 2008
Thanks for the feedback.
Let's see if I get this by writing a little newbie tutorial for myself using
literal Haskell syntax.
I will import Control.Arrow since fmap on a pairs transforms the second
and when transforming pairs, I sometimes need to transform the first
> import Control.Arrow
To understand the (fmap.fmap.fmap) thing, I will create a simple tutorial,
mainly for myself to get a better understanding.
Suppose we have a pair:
> p :: (Bool, [Char])
> p = (True,"Reactive")
The "type graph" (pardon my lack of knowledge of correct terminology) of p
Since fmap is about transforming a structure into another structure,
let's suppose that - given any "instance" with a type signature like p -
we want to create a new instance p' at runtime that transforms
the string (at the second coordinate) into its length.
That's easy; we can use fmap (or Arrow.second) to do that:
< instance Functor ((,) a) where
< fmap f (x,y) = fmap (x, f y)
> tp :: (Bool,[Char]) -> (Bool,Int)
> tp = fmap length
> p' = tp p
fmap on pairs basically transforms the rightmost branch of our graph.
/ \ / \
Bool  --> Bool Int
fmap always transforms the rightmost branch in the graph, since the kind of
Functor is * -> *.
For example lets define an fmap on triples:
> instance Functor ((,,) a b) where
> fmap f (x,y,z) = (x,y,f z)
< fmap :: (c->d) -> (a,b,c) -> (a,b,d)
/ | \ --> / | \
a b c a b d
To continue the (fmap.fmap.fmap) story, suppose we now nest p in a Maybe:
> m :: Maybe (Bool, [Char])
> m = Just (True, "Reactive")
Again we want to transform the string into its length.
To do that we can use the fmap Maybe instance:
< fmap f Nothing = Nothing
< fmap f (Just x) = Just (f x)
The function we need to fmap on m is just tp!
> tm :: Maybe (Bool,[Char]) -> Maybe (Bool,Int)
> tm = fmap tp
> m' = tm m
So again this fmap transforms the rightmost branch underneath the Maybe
(which is the one and only branch underneath the unary Maybe type)
If we expand tm we get
< tm = fmap (fmap length) = (fmap . fmap) length
So here we have the first magical (fmap . fmap):
- the first fmap transforms the branch underneath the Maybe with (fmap (fmap
- the second fmap transforms the right branch underneath the pair (,) with
We can also do this for functions. Suppose we now have
> f :: Char -> Maybe (Bool, [Char])
> f c = Just (c=='a', "Reactive")
The type graph of f is
But function application also has an fmap instance!
It is just the same as function composition:
< instance Functor ((<-) a) where
< fmap f g = f . g
< fmap :: (b->c) -> (a->b) -> (a->c)
/ \ -> / \
a b a c
Again the rightmost branch is transformed...
So to transform the string into its length but now in the f graph, we do
> tf :: (Char -> Maybe (Bool, [Char])) -> (Char -> Maybe (Bool, Int))
> tf = fmap tm
> f' = tf f
Expanding this gives
> tf' = (fmap . fmap . fmap) length
> f'' = tf' f
So the expression ((fmap.fmap.fmap) g) performs deep transformation
on the 3 rightmost branches of any type graph (that has fmap instances)
To transform a leftmost branch, we can use Arrow.first, for example:
> tf'' :: (Char -> Maybe (Bool,[Char])) -> (Char -> Maybe (String,[Char]))
> tf'' = (fmap . fmap . first) show
> f''' = tf'' f
> main = mapM_ putStrLn
> [ showT p $ fmap length
> , showT m $ (fmap.fmap) length
> , showF f $ (fmap.fmap.fmap) length
> , showF f $ (fmap.fmap.first) show ]
> showT x t = show x ++ " ==> " ++ (show $ t x)
> showF f t = "\'a' -> "++show (f 'a') ++ " ==> \'a' -> " ++ show ((t f)
|(True,"Reactive") ==> (True,8)
| Just (True,"Reactive") ==> Just (True,8)
| 'a' -> Just (True,"Reactive") ==> 'a' -> Just (True,8)
| 'a' -> Just (True,"Reactive") ==> 'a' -> Just ("True","Reactive")
I think I learned something cool here: being able to perform deep
transformations without writing a lot of boiler plate code.
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the Reactive