5

The question is not what IO does, but how is it defined, its signature. Specifically, is this data or class, is "a" its type parameter then? I didn't find it anywhere. Also, I don't understand the syntactic meaning of this:

f :: IO a
Alan Coromano
  • 24,958
  • 53
  • 135
  • 205
  • 2
    `IO` is a *type constructor*, there's a bit more [here](http://stackoverflow.com/questions/3155469/help-haskell-functors-sink-in/3164774#3164774) – John L Aug 19 '13 at 03:17

6 Answers6

12

You asked whether IO a is a data type: it is. And you asked whether the a is its type parameter: it is. You said you couldn't find its definition. Let me show you how to find it:

localhost:~ gareth.rowlands$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help
Prelude> :i IO
newtype IO a
  = GHC.Types.IO (GHC.Prim.State# GHC.Prim.RealWorld
                  -> (# GHC.Prim.State# GHC.Prim.RealWorld, a #))
    -- Defined in `GHC.Types'
instance Monad IO -- Defined in `GHC.Base'
instance Functor IO -- Defined in `GHC.Base'
Prelude> 

In ghci, :i or :info tells you about a type. It shows the type declaration and where it's defined. You can see that IO is a Monad and a Functor too.

This technique is more useful on normal Haskell types - as others have noted, IO is magic in Haskell. In a typical Haskell type, the type signature is very revealing but the important thing to know about IO is not its type declaration, rather that IO actions actually perform IO. They do this in a pretty conventional way, typically by calling the underlying C or OS routine. For example, Haskell's putChar action might call C's putchar function.

GarethR
  • 724
  • 5
  • 7
4

IO is a polymorphic type (which happens to be an instance of Monad, irrelevant here).

Consider the humble list. If we were to write our own list of Ints, we might do this:

data IntList = Nil | Cons { listHead :: Int, listRest :: IntList }

If you then abstract over what element type it is, you get this:

data List a = Nil | Cons { listHead :: a, listRest :: List a }

As you can see, the return value of listRest is List a. List is a polymorphic type of kind * -> *, which is to say that it takes one type argument to create a concrete type.

In a similar way, IO is a polymorphic type with kind * -> *, which again means it takes one type argument. If you were to define it yourself, it might look like this:

data IO a = IO (RealWorld -> (a, RealWorld))

(definition courtesy of this answer)

Community
  • 1
  • 1
icktoofay
  • 126,289
  • 21
  • 250
  • 231
  • Where is a definition of IO? – Alan Coromano Aug 18 '13 at 02:54
  • @Marius: `IO` is a deeply magic type built-in to Haskell; I'm not sure that how exactly it's defined would help you very much. As I understood your question, you were asking specifically about the syntax relating to polymorphic types, which I've explained using a less-magic type we can create ourselves, a list. – icktoofay Aug 18 '13 at 02:56
  • yes, I want to know whether it's data or class or something else. Is it not possible? – Alan Coromano Aug 18 '13 at 03:11
  • @Marius: It's a type. `data` is one of several ways of declaring a type. I don't know whether `IO` is required to be declared with a `data`, but it will have the same semantics on the type-system level. – icktoofay Aug 18 '13 at 03:21
  • so `data IO` ... what's further? – Alan Coromano Aug 18 '13 at 03:28
  • 1
    It's actually declared as data IO a. What do you mean by "what's further?"? It's sort of hard to tell what it is that you're trying to ask. If you just want to see the full definition of IO, then the best place to look would be [here](http://hackage.haskell.org/packages/archive/base/4.5.0.0/doc/html/System-IO.html). – qaphla Aug 18 '13 at 03:33
  • @Marius: The definition I showed says that `IO` is a polymorphic algebraic data type with one type parameter, `a`. It has one constructor, `IO`. It encapsulates a function from `RealWorld` to a tuple of the type parameter `a` and `RealWorld`. In reality, you can't see its constructor or the data it contains. – icktoofay Aug 18 '13 at 03:34
  • 2
    @MariusKavansky For what it's worth, the definition of `IO`, from the source, is something like `newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))`. – jtobin Aug 18 '13 at 06:48
3

The amount of magic in IO is grossly overestimated: it has some support from compiler and runtime system, but much less than newbies usually expect.

Here is the source file where it is defined:

http://www.haskell.org/ghc/docs/latest/html/libraries/ghc-prim-0.3.0.0/src/GHC-Types.html

newtype IO a
  = IO (State# RealWorld -> (# State# RealWorld, a #))

It is just an optimized version of state monad. If we remove optimization annotations we will see:

data IO a = IO (Realworld -> (Realworld, a))

So basically IO a is a data structure storing a function that takes old real world and returns new real world with io operation performed and a.

Some compiler tricks are necessary mostly to remove Realworld dummy value efficiently.

IO type is an abstract newtype - constructors are not exported, so you cannot bypass library functions, work with it directly and perform nasty things: duplicate RealWorld, create RealWorld out of nothing or escape the monad (write a function of IO a -> a type).

nponeccop
  • 13,527
  • 1
  • 44
  • 106
2

Since IO can be applied to objects of any type a, as it is a polymorphic monad, a is not specified.

If you have some object with type a, then it can be 'wrappered' as an object of type IO a, which you can think of as being an action that gives an object of type a. For example, getChar is of type IO Char, and so when it is called, it has the side effect of (From the program's perspective) generating a character, which comes from stdin.

As another example, putChar has type Char -> IO (), meaning that it takes a char, and then performs some action that gives no output (in the context of the program, though it will print the char given to stdout).

Edit: More explanation of monads:

A monad can be thought of as a 'wrapper type' M, and has two associated functions:
return and >>=.

Given a type a, it is possible to create objects of type M a (IO a in the case of the IO monad), using the return function.

return, therefore, has type a -> M a. Moreover, return attempts not to change the element that it is passed -- if you call return x, you will get a wrappered version of x that contains all of the information of x (Theoretically, at least. This doesn't happen with, for example, the empty monad.)

For example, return "x" will yield an M Char. This is how getChar works -- it yields an IO Char using a return statement, which is then pulled out of its wrapper with <-.

>>=, read as 'bind', is more complicated. It has type M a -> (a -> M b) -> M b, and its role is to take a 'wrappered' object, and a function from the underlying type of that object to another 'wrappered' object, and apply that function to the underlying variable in the first input.

For example, (return 5) >>= (return . (+ 3)) will yield an M Int, which will be the same M Int that would be given by return 8. In this way, any function that can be applied outside of a monad can also be applied inside of it.

To do this, one could take an arbitrary function f :: a -> b, and give the new function g :: M a -> M b as follows:

g x = x >>= (return . f)

Now, for something to be a monad, these operations must also have certain relations -- their definitions as above aren't quite enough.

First: (return x) >>= f must be equivalent to f x. That is, it must be equivalent to perform an operation on x whether it is 'wrapped' in the monad or not.

Second: x >>= return must be equivalent to m. That is, if an object is unwrapped by bind, and then rewrapped by return, it must return to its same state, unchanged.

Third, and finally (x >>= f) >>= g must be equivalent to x >>= (\y -> (f y >>= g) ). That is, function binding is associative (sort of). More accurately, if two functions are bound successively, this must be equivalent to binding the combination thereof.

Now, while this is how monads work, it's not how it's most commonly used, because of the syntactic sugar of do and <-.

Essentially, do begins a long chain of binds, and each <- sort of creates a lambda function that gets bound.

For example,

a = do x <- something
       y <- function x
       return y

is equivalent to

a = something >>= (\x -> (function x) >>= (\y -> return y))

In both cases, something is bound to x, function x is bound to y, and then y is returned to a in the wrapper of the relevant monad.

Sorry for the wall of text, and I hope it explains something. If there's more you need cleared up about this, or something in this explanation is confusing, just ask.

qaphla
  • 4,707
  • 3
  • 20
  • 31
1

This is a very good question, if you ask me. I remember being very confused about this too, maybe this will help...

'IO' is a type constructor, 'IO a' is a type, the 'a' (in 'IO a') is an type variable. The letter 'a' carries no significance, the letter 'b' or 't1' could have been used just as well.

If you look at the definition of the IO type constructor you will see that it is a newtype defined as: GHC.Types.IO (GHC.Prim.State# GHC.Prim.RealWorld -> (# GHC.Prim.State# GHC.Prim.RealWorld, a #))

'f :: IO a' is the type of a function called 'f' of apparently no arguments that returns a result of some unconstrained type in the IO monad. 'in the IO monad' means that f can do some IO (i.e. change the 'RealWorld', where 'change' means replace the provided RealWorld with a new one) while computing its result. The result of f is polymorphic (that's a type variable 'a' not a type constant like 'Int'). A polymorphic result means that in your program it's the caller that determines the type of the result, so used in one place f could return an Int, used in another place it could return a String. 'Unconstrained' means that there's no type class restricting what type can be returned and so any type can be returned.

Why is 'f' a function and not a constant since there are no parameters and Haskell is pure? Because the definition of IO means that 'f :: IO a' could have been written 'f :: GHC.Prim.State# GHC.Prim.RealWorld -> (# GHC.Prim.State# GHC.Prim.RealWorld, a #)' and so in fact has a parameter -- the 'state of the real world'.

hutch
  • 326
  • 1
  • 8
0

In the data IO a a have mainly the same meaning as in Maybe a. But we can't rid of a constructor, like:

fromIO :: IO a -> a
fromIO (IO a) = a

Fortunately we could use this data in Monads, like:

{-# LANGUAGE ScopedTypeVariables #-}
foo = do
   (fromIO :: a) <- (dataIO :: IO a)
    ...
wit
  • 1,612
  • 10
  • 10