Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do use putStrLn for tracing (Haskell)

I am trying to get a Haskell function to show whenever it is applied by adding a call to "putStrLn":

isPrime2 1 = False

isPrime2 n = do
    putStrLn n
    null (filter (==0) (map (mod n) (filter isPrime2 [2..(floor (sqrt(fromIntegral (n-1))))])))

(The ultimate goal is to demonstrate why one version of isPrime is more efficient than another.)

When I load the above code into GHCi, I get the error:

Couldn't match expected type Bool with actual type m0 b0

I'm sure this is a n00b mistake. Could someone tell me the right way to accomplish what I'm trying to do?

like image 840
Ellen Spertus Avatar asked Oct 04 '11 15:10

Ellen Spertus


2 Answers

The problem is, that Haskell has a strict distinction between pure functions such as (+) and map and impure actions such as putStrLn and main. A pure function is supposed to always yield the same result when given the same input and not modifying anything. This obviously prohibits uses of PutStr and friends. The type system actually enforces this separation. Each function that does IO or is impure in any way has an IO sticking in front of it's type.


tl;dr; use trace from the module Debug.Trace:

import Debug.Trace

isPrime2 1 = False
isPrime2 n = show n `trace` null (filter (==0) (map (mod n) (filter isPrime2 [2..(floor (sqrt(fromIntegral (n-1))))])))

But beware that the results may be rather surprising as there is no guarantee that your code will actually run; the argument of trace may run once or twice or any other number of times.

like image 69
fuz Avatar answered Nov 15 '22 09:11

fuz


Whenever you have these kinds of type errors like Couldn't match expected type X with actual type Y you should use the haskell type system to guide you.
So let's see what is the problem:

You have a pure function with the type Int -> Bool. And you want to print some debug output which is clearly not pure (i.e. which lives in the IO Monad).
But anyway what you want to write is s.th. along those lines:

foo x 
  | x > 0 = debug "ok" True
  | otherwise = debug "ohhh...no" False

Still, the type of your function should be foo :: Int -> Bool

So let's define a debug function that will satisfy the type-checker. It would have to take a String (your debug message) and a Bool (your result) and only evaluate to the Bool.

debug :: String -> Bool -> Bool
debug = undefined

But if we try to implement it, it kind of does not work since we can't escape the IO Monad since the type of putStrLn is putStrLn :: String -> IO (). In order to combine it with evaluating to a Bool we will have to put the Bool in the context of the IO too:

debugIO msg result = putStrLn msg >> return result

Ok, let's ask ghci for the type of this function:

Main> :t debugIO
debugIO :: String -> b -> IO b

So we get an IO Bool but would need just a Bool.
Is there a function with the type IO b -> b? A quick lookup on hoogle gives us a hint:

The infamous unsafePerformIO :: IO a -> a has the type we need here.
So now we could implement our debug function in terms of debugIO:

debug :: String -> Bool -> Bool
debug s r = unsafePerformIO $ debugIO s r

which actually is pretty much what you get with the trace function in the Debug.Trace package as already pointed out by FUZxxl.
And since we agree that one should never use unsafePerformIO the usage of the trace function is preferred. Just keep in mind that despite it's pure type signature it actually is also not referential transparent and uses unsafePerformIO underneath.

like image 33
oliver Avatar answered Nov 15 '22 08:11

oliver