Reputation: 4609
I'm learning Haskell and started noticing common suffixes in functions like:
debugM
mapM_
mapCE
Which is known as Hungarian Notation. But at the same time I can use type classes to write non-ambiguous code like:
show
return
Since functions like map
are so common and used in many contexts, why not let type checker to pick correct polymorphic version of map
, fmap
, mapM
, mapM_
or mapCE
?
Upvotes: 14
Views: 666
Reputation: 74334
There's a little bit of "Hungarian notation", but it's quite different. In short, Haskell's type system removes the need for most of it.
The map
/mapM
thing is a neat example. These two functions confer the exact same concept, but cannot be polymorphically represented because abstracting over the difference would be really noisy. So we pick a Hungarian notation instead.
To be clear, the two types are
map :: (a -> b) -> ([a] -> [b])
mapM :: Monad m => (a -> m b) -> ([a] -> m [b])
These look similar, all mapM
does is add the monad, but not the same. The structure is revealed when you make the following synonyms
type Arr a b = a -> b
type Klei m a b = a -> m b
and rewrite the types as
map :: Arr a b -> Arr [a] [b]
mapM :: Monad m => Klei m a b -> Klei m [a] [b]
The thing to note is that Arr
and Monad m => Klei m
are often extremely similar things. They both form a certain structure known as a "category" which allows us to hoist all kinds of computation inside of it. [0]
What we'd like is to abstract over the choice of category with something like
class Mapping cat where
map :: cat a b -> cat [a] [b]
instance Mapping (->) where map = Prelude.map
instance Monad m => Mapping (Klei m) where map = mapM -- in spirit anyway
but it turns out that there is way more to be gained by abstracting over the list part with Functor
[1]
class Functor f where
map :: (a -> b) -> (f a -> f b)
instance Functor [] where
map = Prelude.map
instance Functor Maybe where
map Nothing = Nothing
map (Just a) = Just (f a)
and so for simplicity's sake, we use Hungarian notation to make the difference of category instead of rolling it up into Haskell's polymorphism functionality.
[0] Notably, the fact that Klei m
is a category implies m
is a monad and the category laws become exactly the monad laws. In particular, that's my favorite way for remembering what the monad laws are.
[1] Technically, the sole method of Functor
is called fmap
not map
, but it could and perhaps should have been called just map
. The f
was added so that the type signature for map
remains simple (specialized to lists) and thus is a little less intimidating to beginners. Whether or not that was the right decision is a debate that continues today.
Upvotes: 26
Reputation: 15673
Your assumption is that all of these do roughly the same thing - they don't. map
and fmap
are pretty much the same function - map
is just fmap
specialized to the []
functor (either for historical reasons, or so that beginners would get less confusing type errors - I'm not sure).
mapM
and mapM_
on the other hand are like map
followed by sequence
or sequence_
respectively - while what they're doing may look related, they're doing different things. Incidentally, the function that behaves like fmap
for monads is... fmap
(which is also aliased with a specialized signature to liftM
, for historical reasons), as Monad
s are, by definition, also Functor
s; note that this is, right now, not enforced by the standard library - a historical oversight that should be corrected with GHC 7.10 if I'm not mistaken.
I don't know what to tell you about debugM
and mapCE
as I haven't seen these before.
Upvotes: 8