How do use putStrLn for tracing (Haskell)
Asked Answered
B

2

5

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?

Birkett answered 4/10, 2011 at 15:15 Comment(2)
Why are you subtracting 1 from n before taking its square root? That will only work for numbers that aren't squares of primes and will fail for, e.g., 25.Headward
@jwodder, thanks for the correct to the primes algorithm.Birkett
R
18

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.

Remanence answered 4/10, 2011 at 15:25 Comment(0)
T
4

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.

Tailback answered 4/10, 2011 at 17:27 Comment(7)
Please note, that you should never use the function unsafePerformIO. There is nearly always a better solution. This function is evil; it allows you to do things that are otherwise impossible and to destroy some safety assumptions the compiler could make otherwise. If you use unsafePerformIO, think twice and if you are not 100% sure, don't use it! There are some legitimate use cases, like trace, but they are rare.Remanence
@FUZxxl: I'm well aware of the merits and dangers of unsafePerformIO. But in order to implement what espertus was asking for I don't see any other option. As you pointed out, trace is in fact implemented in terms of unsafePerformIO. I'd say it falls into the same category of code that should usually be treated with a long stick.Tailback
And would you also say that you should never use trace, except for debugging? For example, you should never use it to print information that users are supposed to read when the code is in production?Valer
@MatrixFrog: exactly. trace should only be used for quick and dirty debugging. (with an emphasis on dirty). In situations where you are dealing with user visible output you most probably already have to run some code in the IO Monad. The trace function is, like unsafePerformIO, not referentially transparent. You might not always see your user messages as you would expect them.Tailback
If you just enter the type of your debug function into Hoogle, you get trace as the first hit, so you could have stopped there instead of introducing unsafePerformIO (the function whose name must never be spoken!)Actinic
@Tailback Can I add a small remark that one should never ever use The Function That Must Not Be Named?Remanence
@FUZxxl sure. Programming haskell you should never use it. In the case of library writers I'm actually not quite sure...afaict there are usecases where it makes sense. But I'll add your remark to make that more clear.Tailback

© 2022 - 2024 — McMap. All rights reserved.