sevo
sevo

Reputation: 4609

Is Haskell designed to encourage Hungarian Notation?

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

Answers (2)

J. Abrahamson
J. Abrahamson

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

Cubic
Cubic

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 Monads are, by definition, also Functors; 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

Related Questions