James Strieter
James Strieter

Reputation: 819

How to wrap char literal in IO monad in Haskell?

I know you're supposed to wrap up the operations you want to perform on a result in a monad rather than unwrap things from the monad.

What I can't find are any idiot-friendly examples of how to do that.

For example, I want to do something like this:

myFunction = do
    c <- getChar
    if (c == 'q')
        then putStrLn "take action 1"
        else putStrLn "take action 2"

But you can't compare a char literal to an IO Char directly.

GHCi version is 8.4.4.

Error Message:

[1 of 2] Compiling Lib              ( /Users/jamesstrieter/hask-tink/src/Lib.hs, interpreted )

/Users/jamesstrieter/hask-tink/src/Lib.hs:66:18: error:
    • Couldn't match expected type ‘IO char’ with actual type ‘Char’
    • In the second argument of ‘(==)’, namely ‘'q'’
      In the expression: x == 'q'
      In an equation for ‘what2do’: what2do x = x == 'q'
    • Relevant bindings include
        x :: IO char
          (bound at /Users/jamesstrieter/hask-tink/src/Lib.hs:66:9)
        what2do :: IO char -> Bool
          (bound at /Users/jamesstrieter/hask-tink/src/Lib.hs:66:1)
   |
66 | what2do x = x == 'q'
   |                  ^^^
Failed, no modules loaded.

Upvotes: 3

Views: 1412

Answers (3)

chepner
chepner

Reputation: 532093

One of the most important things to understand about the IO monad is that the expression m >>= f does not execute the action m, nor does it ever call the function f.

Instead, it just creates a new IO action that wraps both m and f, which, when executed, will finally execute m, extract the return value, and then execute the next action calculated on the spot by calling f with that value.

That's it. Your entire Haskell program is nothing but a DSL for building a single IO action that gets assigned to main, which the Haskell runtime will execute for you.

So when you write

-- Rewritten slightly for brevity
myFunction = do
    c <- getChar
    putStrLn (if (c == 'q')
        then "take action 1"
        else "take action 2")

this is desugared to

myFunction = getChar >>= (\c -> 
                putStrLn (if (c == 'q') 
                    then "take action 1" 
                    else "take action 2")

and what you are actually saying is "Build an IO action containing getChar and a function of type Char -> IO (), such that when this action is executed, it executes getChar and passes the resulting Char to the function to produce another IO action to be executed immediately."

Upvotes: 2

puhlen
puhlen

Reputation: 8529

The code you posted looks perfectly correct and functional.

do-notation is a way of working with value in monad.

c <- getChar within the do block binds c to the char inside the IO Char you get with getChar. You can compare c == 'q' just fine here because c is a plain char, not an IO Char.

To answer you direct question, you can use the return function to put a pure value into any monad, including IO, so return 'q' "wraps" the character literal 'q' into a monad. This isn't what you want in this case, the code you already have is what you are looking for.

Upvotes: 9

Thomas M. DuBuisson
Thomas M. DuBuisson

Reputation: 64740

But you can't compare a char literal to an IO Char directly.

Sure, but when you "bind" the result of the IO action it is no longer an IO Char but just a Char so that's why it works.

In more words:

Prelude> :t getChar
getChar :: IO Char
Prelude> c <- getChar
x
Prelude> :t c
c :: Char

Upvotes: 6

Related Questions