3

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?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
rnso
  • 23,686
  • 25
  • 112
  • 234
  • 3
    Do you understand what "pure" means in the statement "Haskell is a pure functional language"? – Joseph Sible-Reinstate Monica May 20 '19 at 12:26
  • 1
    I have read/heard about it but do not understand it fully. – rnso May 20 '19 at 12:27
  • `do` notation always gets you some type of `IO` value (e.g. `IO Bool`, in this case), so this is pretty straightforward. – hegel5000 May 20 '19 at 12:50
  • 1
    In short, Haskell has no *statements*. – Willem Van Onsem May 20 '19 at 13:36
  • What is disadvantage of simple statements? Why are they being avoided here so much? – rnso May 20 '19 at 13:41
  • by "statement" in functional programming vernacular is meant something that has no value, and is used for its effect. it is popular to say that "functional" are "expression-oriented" languages where *expressions* are *evaluated* for their value. so in Scheme for example, instead of having an explicit `return` *statement* like they have in C, simply the last evaluated expression's value is returned as the overall expression's value. – Will Ness May 20 '19 at 16:59

3 Answers3

3

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.

Mor A.
  • 3,805
  • 2
  • 16
  • 19
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • Great answer, I would also add a short explanation about `return` in haskell vs. its meaning in other languages as this causes confusion to many learners. – dimid May 21 '19 at 09:29
  • @m-aroosi thanks for the edit! I would often notice this error in posts of others, and here I made it myself. the irony! – Will Ness May 23 '19 at 07:55
2

You might be confused by the same print line working in toTry, but not in isBinary. The difference stems from the declaration:

isBinary :: String -> Bool

This means that isBinary is a pure function (i.e. no side effects), taking a string and returning a boolean. In fact, you could simplify it to

isBinary ss = any (\c -> ord c > 127) ss 

or even use the point-free style

isBinary = any (\c -> ord c > 127)

However, toTry is

toTry :: String -> IO ()

I.e. it takes a string and returns the IO monad which is impure (can have side effects, such as printing text to the console).

Haskell is a language that encourages using pure functions, and enforces it using the type system, by forcing the programmer to explicitly mark impure code.

Further reading: What does "pure" mean in "pure functional language"?

dimid
  • 7,285
  • 1
  • 46
  • 85
2

Looking at your code, it seems your use of print in isBinary is not an integral part of what you want the function to do, but merely a debug print statement that will be removed later on. In that case, you do not want to change the type of isBinary to String -> IO Bool (for more on that, see Will Ness' answer), as you don't actually need IO except for debugging. Rather, the core libraries offer the Debug.Trace module, which caters to this kind of situation. With it, we can add your debug print statement like this:

isBinary :: String -> Bool
isBinary ss = trace "In isBinary fn" $ any (\c -> ord c > 127) ss

Then, once you are done debugging, you can remove the use of trace -- and it bears repeating you really should do that later on. Quoting the Debug.Trace documentation:

Functions for tracing and monitoring execution.

These can be useful for investigating bugs or performance problems. They should not be used in production code.

Community
  • 1
  • 1
duplode
  • 33,731
  • 7
  • 79
  • 150