3

Take the function getLine - it has the type:

getLine :: IO String

How do I extract the String from this IO action?


More generally, how do I convert this:

IO a

to this:

a

If this is not possible, then why can't I do it?

atravers
  • 455
  • 4
  • 8
pikapika
  • 376
  • 1
  • 7
  • 7
    Because if you could do this, there'd be no point in having an IO type in the first place. – sepp2k Feb 26 '17 at 13:45
  • Would-be duplicate: [*How can I parse the IO String in Haskell?*](http://stackoverflow.com/q/11229854/2751851) (I'm not closing this myself because it might make for a better duplicate target). – duplode Feb 26 '17 at 16:08
  • Possible duplicate of https://stackoverflow.com/questions/7154518/unwrapping-a-monad . – atravers Nov 17 '20 at 07:47

4 Answers4

10

In Haskell, when you want to work with a value that is "trapped" in IO, you don't take the value out of IO. Instead, you put the operation you want to perform into IO, as well!

For example, suppose you want to check how many characters the getLine :: IO String will produce, using the length function from Prelude.

There exists a helper function called fmap which, when specialized to IO, has the type:

fmap :: (a -> b) -> IO a -> IO b

It takes a function that works on "pure" values not trapped in IO, and gives you a function that works with values that are trapped in IO. This means that the code

fmap length getLine :: IO Int

represents an IO action that reads a line from console and then gives you its length.

<$> is an infix synonym for fmap that can make things simpler. This is equivalent to the above code:

length <$> getLine

Now, sometimes the operation you want to perform with the IO-trapped value itself returns an IO-trapped value. Simple example: you wan to write back the string you have just read using putStrLn :: String -> IO ().

In that case, fmap is not enough. You need to use the (>>=) operator, which, when specialiced to IO, has the type IO a -> (a -> IO b) -> IO b. In out case:

getLine >>= putStrLn :: IO ()

Using (>>=) to chain IO actions has an imperative, sequential flavor. There is a kind of syntactic sugar called "do-notation" which helps to write sequential operation like these in a more natural way:

do line <- getLine
   putStrLn line

Notice that the <- here is not an operator, but part of the syntactic sugar provided by the do notation.

danidiaz
  • 26,936
  • 4
  • 45
  • 95
  • 1
    Great simple explanation for this common question! "Instead, you put the operation you want to perform into IO, as well!" – luqui Feb 27 '17 at 03:37
2

Not going into any details, if you're in a do block, you can (informally/inaccurately) consider <- as getting the value out of the IO.

For example, the following function takes a line from getLine, and passes it to a pure function that just takes a String

main = do
  line <- getLine
  putStrLn (wrap line)

wrap :: String -> String
wrap line = "'" ++ line ++ "'"

If you compile this as wrap, and on the command line run

echo "Hello" | wrap

you should see

'Hello'
Michal Charemza
  • 25,940
  • 14
  • 98
  • 165
1

If you know C then consider the question "How can I get the string from gets?" An IO String is not some string that's made hard to get to, it's a procedure that can return a string - like reading from a network or stdin. You want to run the procedure to obtain a string.

A common way to run IO actions in a sequence is do notation:

main = do
   someString <- getLine
   -- someString :: String
   print someString

In the above you run the getLine operation to obtain a String value then use the value however you wish.

Thomas M. DuBuisson
  • 64,245
  • 7
  • 109
  • 166
0

So "generally", it's unclear why you think you need a function of this type and in this case it makes all the difference.

It should be noted for completeness that it is possible. There indeed exists a function of type IO a -> a in the base library called unsafePerformIO.

But the unsafe part is there for a reason. There are few situations where its usage would be considered justified. It's an escape hatch to be used with great caution - most of the time you will let monsters in instead of letting yourself out.

Why can't you normally go from IO a to a? Well at the very least it allows you to break the rules by having a seemingly pure function that is not pure at all - ouch! If it were a common practice to do this the type signatures and all the work done by the compiler to verify them would make no sense at all. All the correctness guarantees would go out of the window.

Haskell is, partly, interesting precisely because this is (normally) impossible.

For how to approach your getLine problem in particular see the other answers.

user2847643
  • 2,893
  • 14
  • 22