2
votes

I have this test:

testReadFile = runTestTT $ TestLabel "InteractionDomain.readFileContentsToList" (TestList [
    (TestLabel "testing infrastructure: read test file" (TestList [
        TestCase (withTempFileContainingText (\fileHandle ->
            assertEqual "contents as expected" "The dog barks at midnight." (do 
                content <- hGetContents fileHandle
                return content)))]))])

and (in case it helps) withTempFileContainingText is this function

withTempFileContainingText lambda = 
        bracketOnError 
            (do (path, fileHandle) <- openTempFile "." "input.txt"
                return fileHandle)
            (deleteFile)
            (\fileHandle -> do 
                hPutStr fileHandle "The dog barks at midnight."
                hClose fileHandle --hoping this flushes buffers
                return (lambda fileHandle))

ghc complains that

InteractionDomain.hs:36:4:
    Couldn't match expected type `IO String'
           against inferred type `[String]'
    In a stmt of a 'do' expression: content ≤- hGetContents fileHandle

Which I don't understand. Why is there the inference that content is a list of String? And what should this code look like?

3
Lisp programmer, by any chance? :)J Cooper

3 Answers

4
votes

Well, what I see right away is a type mismatch in your assertion.

assertEqual is String -> a -> a -> Assertion. You pass a String as the second argument, meaning a String should also be the third argument. However, your do expression is not returning String, but rather IO String.

Edit: To expand, once you mess with IO, you can't ever ditch it. You correctly extract the value out of IO with your <-, but then immediately wrap it back into IO with return. If you want to use the string, you must do it inside IO, something like:

do
  contents <- hGetContents handle
  assertEqual "They're equal" "Expected string" contents

Note that then your do would be returning IO Assertion. If you want to use the assertion value, you'd need to unwrap it similarly, and so on. Haskell doesn't let you get away with (hidden) side effects!

3
votes

The type of assertEqual is

assertEqual :: String -> a -> a -> Assertion

Since the 2nd argument “"The dog barks at midnight."” is a String, we know a must be String. Therefore the do expression

do 
  content <- hGetContents fileHandle
  return content

must return a String. But String = [Char], therefore Haskell will regard this as a List monad. I don't know why it infers a [String] instead of a [Char] = String, but this at least explains where the List comes.


BTW, an Assertion is already an IO (). If you return an assertion you'll get an IO (IO ()). Probably you mean

withTempFileContainingText :: (Handle -> IO c) -> IO c
withTempFileContainingText lambda = 
    bracketOnError 
        (return . snd =<< openTempFile "." "input.txt")
        (deleteFile)
        (\fileHandle -> do
            hPutStr fileHandle "The dog barks at midnight."
            hClose fileHandle
            lambda fileHandle)   -- # <--- No return here

then the testCase can be written as

testCase :: Assertion
testCase = withTempFileContainingText $ \fileHandle -> do
    conts <- hGetContents fileHandle
    assertEqual "contents as expected" "The dog barks at midnight." conts
    -- # ^-- No return here.
0
votes

Thanks all for some valuable pointers.

This runs and passes:

withTempFileContainingText lambda = 
        bracketOnError 
            (openTempFile "." "input.txt")
            (\(filePath, fileHandle) -> do
                removeFile filePath)
            (\(filePath, fileHandle) -> do
                startOfFile <- hGetPosn fileHandle 
                hPutStr fileHandle "The dog barks at midnight."
                hFlush fileHandle
                hSetPosn startOfFile
                lambda fileHandle
                removeFile filePath)


testReadFile = runTestTT $ TestLabel "InteractionDomain.readFileContentsToList" (TestList [
    (TestLabel "testing infrastructure: read test file" (TestList [
        TestCase (withTempFileContainingText (\fileHandle -> do
            content <- hGetContents fileHandle 
            assertEqual "contents as expected" "The dog barks at midnight." content))]))])