[Haskell-beginners] Monads

MAN elviotoccalino at gmail.com
Wed Mar 2 18:08:28 CET 2011


El mié, 02-03-2011 a las 10:04 -0500, Patrick Lynch escribió:
> I think my problem that I'm facing is that I don't see how the instance 
> Functor Maybe is derived from the class Functor...

>   I don't see how the instance Functor Maybe is derived [presumably: 'f' is 
> function (a->b) and not functor 'f' as shown in the class description):
>      instance Functor Maybe where
>        fmap f (Just x) = Just (f x)
>        fmap f Nothing = Nothing

Perhaps it'll serve you well to think of the monad as a context for
computation. In the case of Maybe, it allows you to represent failure in
your computations.
Warning: lengthy mail piece ahead!

Suppose you've build a chain of computations, each segregated in its own
function. Some of those functions can fail, some others not. In a
different language you might use a special value to represent failure:
an empty string, an emtpy list, zero, some language-specific contruct
a-la NULL, etc. That approach has its cons, though: you have to check
for that particular value in each link of your chain, and since this
fail-value is tied to the return type, different special values will be
used throughout your chain... which is cumbersome, and confusing.
The Maybe type allows to represent failure through a much cleaner
(smarter) mechanism: You build your chain of computations in the Maybe
type. By using it as a monad, successful links will wrap its return
value in 'Just', and whichever link in your chain failing will return
'Nothing' (regardless of what it's working on, be it strings or numbers
or what not). As a result, your whole chain will short-circuit at that
failing link. The rest of the computations are never run.
As an example, suppose you've built a function 'fn', which type is 'a'
-> 'b', and your chain of functions so far gives you that 'a'... But
some of the functions running before 'fn' may fail (signaling, for
example, invalid input). Instead of choosing a particular value 'a' to
signal that error, you decide to wrap your functions in Maybe, and use
Maybe as a monad. Now 'fn' will recieve a 'Maybe a'... Assuming 'fn'
never fails (a situation quite common in long chains), you don't need to
return a 'Maybe b', so:

fn :: Maybe a -> b
fn Nothing = someValueNeverUsed
fn Just someValue = fn someValue

And rely in previous computations to sanitaze fn's input .But, what if
links running _after_ wrapFN can also fail? They live in the Maybe monad
too. So, it can be advantageous to return the results of 'fn' in Maybe,
even if 'fn' will never fail:

wrapFN :: Maybe a -> Maybe b
wrapFN ma = do
  res <- ma
  case res of Nothing -> Nothing
              Just bs -> Just $ fn bs

You see, we run action 'ma' and act according to its results. If 'ma'
returns Nothing, we'll feed the next link in the chain Nothing,
propagating failure. Notice that, in this case 'fn' is never used, never
runs. This saves resources.
Assuming all other links in the chain follow a similar bahaviour, your
chain now short-circuits on failure. Hurra!

But really, what a bummer, wrappers everywhere? Enter 'fmap' :

wrapFN :: Maybe a -> Maybe b
wrapFN ma = fn `fmap` ma
-- or in point free:   wrapFN = fmap fn

Which provides the same functionality. If before you where doing
something like:
maybeActionA >>= maybeActionB >>= wrapperFN >>= maybeActionC

now you do:
maybeActionA >>= maybeActionB >>= fmap fn >>= maybeActionC

Which shows how you've "lifted" the function 'fn' into the Maybe monad,
with little effort thanks to 'fmap'. In order for the above to work, you
need the Functor Maybe instance. Review it now, see if it makes more
sense: 

     instance Functor Maybe where
       fmap f (Just x) = Just (f x) -- wrap successful computation
       fmap f Nothing = Nothing     -- propagate failure




More information about the Beginners mailing list