Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I wish the author had explained what the difference between using the IO type and using the IO type via the monadic interface looked like. As a Haskell beginner, this article just makes me more confused. Can someone share an example or clarify?


If you are starting Haskell, there is a wonderful little function you can use called `interact`, which has a type `(String -> String) -> IO ()`. This takes a String to String function, and turns it into an IO type that reads from STDIN, runs the function, and writes to STDOUT, lazily. Thus, you can just write `main = interact myFunction` and get everything you need to build complex Unix pipeline utilities without needing to worry about any impurity or composition. I suggest you stick to this until you are comfortable with the basic syntax, types, lazyness, and typeclasses of Haskell.

The problem with learning about the monad typeclass first is that it is a very very high level abstraction, and abstractions are near impossible to understand without a working understanding of examples that they might abstract.


This sounds amazing. I was one of those beginners who thought I had to learn monads right away to get anything done, became frustrated, and never progressed.


(Also a Haskell beginner)

I think his point is that the IO type may be an example of a monad, but shut up about the monad part because it's not relevant to learning how to use it.


Exactly. You only need to know about some primitive operations (like putStrLn and getLine), and ways to combine them (like >>, >>=, and return).

This was my point when I wrote my "IO tutorial" (not a monad tutorial!): http://blog.raek.se/2012/10/19/haskell-io-in-five-minutes/


You don't use another interface when thinking "IO type" instead of "IO monad". It's all about what words you use to explain the types of the interface.

For example, when you write 1 + 2 you are probably thinking of (+) being addition on integers. But a mathematician could also think of (+) as the operator from the group (Z, +, 0). Groups (a concept in abstract algebra) generalize integers, but you do not need to know group theory to do arithmetic. But it's the same operator nevertheless! Just two ways of talking about it.

Similarly, when you write putStrln "Hello " >> putStrLn "world!", the "IO type" way of thinking says: (>>) is an operator that glues two IO actions together. The resulting combined action performs the argument actions sequentially from left to right. The operator has the type IO a -> IO b -> IO b.

But you can think of (>>) in more general terms. It is not meaningful only for the IO type. It is meaningful for any type that happens to be a monad! That insight may be useful eventually, but probably not for a beginner Haskell programmer just wanting to do some IO!


Yes.

Here's a function that has type `IO ()` but doesn't obviously use `IO`'s monadic interface.

    foo = print "hello"
`print` may use `IO`'s monadic interface under the hood, I don't know.

Here's some code that has type `IO ()` and does use `IO`'s monadic interface.

   foo = do
       str <- getLine
       print str
which is de-sugared to

    foo = getLine >>= \str -> print str
which you can, if you like concise code, feel free to shorten to

    foo = getLine >>= print
The important part is `>>=` (pronounced "bind"), which is defined in the monad typeclass. (A typeclass is like a Java interface, by the way.)


Or:

    foo = print =<< getLine
which often reads better when trying to build something that looks more like a nested function call and less like a pipeline.


The IO type can be constructed in all kinds of ways. For instance, using `putStrLn` is probably the most common

    putStrLn "hello world" :: IO ()
As IO is an instance of Functor we can use that interface, for instance

    fmap (const 1) (putStrLn "hello world") :: IO Int
That would be using IO's functorial interface. As an extension to a functorial interface we also have a monadic interface. This affords us a "most boring" way of creating new IO values

    -- very different from putStrLn
    -- it doesn't actually represent *doing* anything at all
    return "hello world" :: IO String
and also, finally, a way at getting at values "inside" of IO and therefore "compose" values of IO

    -- getLine :: IO String

    capitalizer :: IO ()
    capitalizer = getLine >>= (\line -> putStrLn (map toUpper line))
So, the emphasis is that IO is "just" another type. One shouldn't refer to "the IO monad" as anything privileged unless you're actively referring to using (return, >>=) with IO. Generally "the IO monad" explains very little of IO's power as "being a monad" is a very small part of any type's interface.

---

To be clear, here are some other interfaces we can use from IO (besides just other IO value constructors)

* combining IO with exception handlers with

    catch :: IO a -> (e -> IO a) -> IO a
* forking new threads with

    forkIO :: IO () -> IO ThreadId
* embedding IO into another type with

    -- extremely common
    liftIO :: MonadIO m => IO a -> m a
* converting an IO action into an asynchronous action with

    async :: IO a -> IO (Async a)
* assigning a IO action pointed at by a finalizer to a memory pointer with

    newForeignPtr :: FunPtr (Ptr a -> IO ()) -> Ptr a -> IO (ForeignPtr a)


non-monadic:

    import Data.Char

    main = (fmap.fmap) toUpper getLine
monadic (but still doesn't utilize extra power granted by monadic interface, since it produces the same result as above):

    import Data.Char

    main = do
      x <- getLine
      return $ map toUpper x


Think of a value with type (IO String) as "a computation that interacts with the outside world and then returns a value of type String", and similarly (IO ()) is "a computation that interacts with the outside world and then does not return a value. putStrLn has type IO (). getLine has type IO String.

Now, imagine that you want to build up a whole program that interacts with the outside world. One easy way to do this is to sequence together a number of these primitive operations. The monadic bind operator is Haskell's way of sequencing together computations that support the monad interface (which IO does). A simple console echo program could be written as "main = getLine >>= putStrLn", using the >>= operator to feed the result of getLine into putStrLn. The two computations, sequenced together, form the definition of the whole program.

The monadic bind operator gets awkward if you're trying to combine lots of these statements or if you need to assign their results to variables, and so do-notation was added to the language:

  main = do
    youTyped <- readLine
    putStrLn youTyped
This is because a monad functions just like an overridable semicolon: it's a way for you to define how two computations, specified in sequence, will execute. For the IO type, this is specified by the language runtime to be "execute the first, then execute the second", but it doesn't have to be this. For the Maybe type, for example, it's "Execute the first, but only execute the second if the result of the first is not Nothing", and for the Error type, it's "Execute the first, but propagate the error if it returns one."

A key point the article is making but fails to mention is that these values of the IO type are just ordinary values. The "normal" thing to do is execute them in sequence, but you don't have to do that. You can, for example, map putStrLn over a list and then sequence that with mapM:

  main = mapM putStrLn [1,2,3,4]
Or you could just map putStrLn over a list, and then pass the resulting list of IO values around to some other filter for further processing before sequencing. A somewhat roundabout way of defining FizzBuzz, for example, would be:

  subst (i, a)
    | i `mod` 15 == 0 = putStrLn "FizzBuzz"
    | i `mod` 5 == 0 = putStrLn "Buzz"
    | i `mod` 3 == 0 = putStrLn "Fizz"
    | otherwise = a
  makePrint i = (i, putStrLn $ show i)
  main = sequence_ $ map (subst . makePrint) [1..100]
See what I did there? The numbers 1 through 100 are each first passed through makePrint, which pairs them with an IO action to print their string equivalent. The result of that is passed to subst, which filters out multiples of 3, 5, and 15 to instead become actions that print "Fizz", "Buzz", and "FizzBuzz". The original putStrLn calls are dropped on the floor, or more accurately, never executed because Haskell is lazy. Finally, all of the resulting actions are sequenced together with sequence_, which is a standard library function to execute a bunch of monads and throw away the result. The result of putStrLn is just a value, and can be manipulated like any other value.

This is how many of the cooler monadic libraries (eg. stateful CGI programming with WASH, parser combinators in Parsec, or bytecode JIT generation with LLVM) are implemented.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: