Search code examples
haskellbooleanmonadsio-monaddo-notation

Why can't I put a print function statement here?


I am trying following code with try-catch block:

import System.Environment  
import System.IO  
import System.IO.Error  
import Control.Exception

isBinary :: String -> Bool
isBinary ss = do 
    print "In isBinary fn"   -- works if this line is removed.
    let ans = any (\c -> ord c > 127) ss
    ans

toTry :: String -> IO ()  
toTry firline = do
        print "In toTry fn."
        let answer = isBinary firline
        if not answer then do
            print "Sent line not binary: "
        else
            print "Sent line binary"

handler :: IOError -> IO ()  
handler e = putStrLn "Whoops, had some trouble!"  

ss = "this is a test"
main = do 
    toTry ss `catch` handler

However, I am getting following error:

$ runghc trycatch3.hs 

trycatch3.hs:9:9: error:
    • Couldn't match expected type ‘Bool’ with actual type ‘IO Bool’
    • In a stmt of a 'do' block: print "in isBinary fn"
      In the expression:
        do { print "in isBinary fn";
             let ans = any (\ c -> ...) ss;
             return ans }
      In an equation for ‘isBinary’:
          isBinary ss
            = do { print "in isBinary fn";
                   let ans = ...;
                   return ans }

trycatch3.hs:10:30: error:
    • Variable not in scope: ord :: Char -> Integer
    • Perhaps you meant one of these:
        ‘or’ (imported from Prelude), ‘odd’ (imported from Prelude)

The error goes away and program works well if the print statement is removed from isBinary function.

Why can't I put print statement in this function?


Solution

  • The answer is, "because types". Specifically:

    isBinary :: String -> Bool
    isBinary ss = do 
      ....
    

    Since it's a do block, the return type of isBinary must match a monadic type Monad m => m t for some m and some t. Here, since print "" :: IO (), m is IO, so it should've been

    isBinary :: String -> IO Bool
    isBinary ss = do 
    

    and now

        print "In isBinary fn"                 -- works
        let ans = any (\c -> ord c > 127) ss   -- also works
        ans                                    -- doesn't work
    

    ans doesn't work because of types, again. Its type is Bool, but it must be IO Bool -- first, because this do block belongs to IO monad, on account of print; and second, because of the return type of the function as a whole.

    Instead, use

        return ans
    

    and now it'll work, because return injects a value into the monadic context, and being the last do block value it becomes the value produced by the do block overall (if return val appears in the middle it just passes the val to the next step in the combined computation).

    The function toTry will have to be augmented to use the new definition:

    toTry :: String -> IO ()  
    toTry firline = do
            print "In toTry fn."
            -- let answer = isBinary firline    -- incorrect, now!
            answer <- isBinary firline          -- isBinary ... :: IO Bool
            if not answer then do               --       answer ::    Bool
                print "Sent line not binary: "
            else
                print "Sent line binary"
    

    m a on the right of <-, a on the left.

    See this for a general description of do notation.