[Haskell-beginners] Re: monad nomad gonad gomad

Ertugrul Soeylemez es at ertes.de
Tue Aug 17 20:50:18 EDT 2010

prad <prad at towardsfreedom.com> wrote:

> On Sun, 15 Aug 2010 21:11:52 +0200
> Ertugrul Soeylemez <es at ertes.de> wrote:
> > Just remember this:  You have an IO computation?  You want to refer to
> > its result?  Fine, just use '<-'.  That's it.
> that's what i've been doing the past two days and it works ... except
> in one case.
> for example, i have:
> ========
>     flData <- readFile key
>     let (sec:tle:kwd:dsc:url:typ:dat:txt) = lines flData
> ========
> so flData is the computation which reads the contents of a file and i
> can take that and do a lines on it. no problem.

I have the impression that you have understood the problem, but you have
yet to realize that you have.  Your 'subs' function needs the contents
of a file, so either you pass those contents explicitly or it needs to
become an IO computation, so it can read them itself.

Anyway, honestly I don't understand what your 'subs' function is about.
It seems to interpret stuff after "```", but you can write this a whole
lot simpler and probably more correct, although still very fragile:

  subs :: [String] -> [String]
  subs [] = []
  subs ("```" : code : ts) = gt code : subs ts
  subs (t:ts) = t : subs ts

Why is this fragile?  Well, try the following:

  subs ["```"]

Also do yourself and others a favor and write type annotations at least
for all top level definitions.  Yes, Haskell has type inference, but for
important parts of your code you should really write explicit type

Reason:  First of all, the types of your functions are the specification
of your program.  To write a function, the very first thing you should
do is to write its type signature.  Never start with the function
definition.  Sometimes you write a valid function, which doesn't match
your intended specification.  Also given the type signature you can
reason much better about whether your code really does what it should
do, without even compiling it.

Secondly type annotations make your program far more readable.  For some
really simple one-liner functions they aren't strictly necessary for
readability, but personally I write them even for the simplest

Now what's the type of your function, when it should read a file?  Think
about it for a while.  One very easy way is to give it the contents of
the file as a parameter:

  subs :: String -> [String] -> [String]

Another reasonable way is to let the function read the file itself:

  subs :: FilePath -> [String] -> IO [String]

But beware:  The way you were going to do it is very bad.  It would read
the file once for each occurence of "```".  Better read the file at the
beginning only, at which point you can just as well split this into two

  subs          :: String -> [String] -> [String]
  subsUsingFile :: FilePath -> [String] -> IO [String]

This particular way to make a nonmonadic functions monadic is called
lifting, and because it is such a common thing to do, there are loads of
combinators to do it, most notably liftM, fmap and (<$>), which are all
the same (though liftM can be used in monads only):

  liftM :: Monad m   => (a -> b) -> (m a -> m b)
  fmap  :: Functor f => (a -> b) -> (f a -> f b)
  (<$>) :: Functor f => (a -> b) -> (f a -> f b)

The subsUsingFile function can be implemented in one of the following

  -- Raw:
  subsUsingFile fn ts = do
    content <- readFile fn
    return (subs content ts)

  -- Using lifting:
  subsUsingFile fn ts = liftM (`subs` ts) $ readFile fn
  subsUsingFile fn ts = fmap  (`subs` ts) $ readFile fn
  subsUsingFile fn ts = (`subs` ts) <$> readFile fn

Further note that you are overusing do-notation, even for nonmonadic
code.  Read section 6 of my tutorial [1] or a HaskellWiki article about
this [2].  Whenever you see this pattern:

  do x <- c
     return (f x)

you should consider using lifting:

  f <$> c

and don't use do-notation if all you want to do is to make an equation:

  let x = y
  in ...

especially when your code is not monadic.  Your use of the do-notation
in your 'subs' function works only incidentally, because it is indeed a
monadic function, but not in the IO monad, but in the list monad.

[1] http://ertes.de/articles/monads.html#section-9
[2] http://www.haskell.org/haskellwiki/Do_notation_considered_harmful


nightmare = unsafePerformIO (getWrongWife >>= sex)

More information about the Beginners mailing list