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
?
Hungarian Notation can be useful in languages without compile-time type checking, as it would allow developer to quickly remind herself of how the particular variable is used. It does nothing for performance or behavior. It is supposed to improve code readability and is mostly a matter a taste and coding style.
The name of the notation is a reference to Simonyi's nation of origin; Hungarian people's names are "reversed" compared to most other European names; the family name precedes the given name. For example, the anglicized name "Charles Simonyi" in Hungarian was originally "Simonyi Károly".
What is Hungarian Notation? Hungarian is a naming convention for identifiers in code, especially, but not exclusively, the C++ and Java languages. Each identifier would have two parts to it, a type and a qualifier. type: the first characters of the identifier specify what type the object is an instance of.
Hungarian notation is an identifier naming convention in computer programming, in which the name of a variable or function indicates its intention or kind, and in some dialects its type.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With