Problem: Process a CSV file and test a condition on it. Current code simply prints instead of testing for the condition.
Issue: Type inference fails. I do not follow why it fails.
Here’s the code, less the import boilerplate.
--------------------------------------------------
has_empty_string :: [String] -> Bool
has_empty_string col =
any null col
----------------------------------------
get_hashrow :: [[String]] -> [String]
get_hashrow sheet =
-- looking at column 5
map (\row -> row !! 5) sheet
------------------------------
process_lines :: (String -> b) -> Handle -> IO ()
process_lines func inh = do
ineof <- hIsEOF inh
if ineof
then return ()
else do inpStr <- hGetLine inh
result <- func inpStr
putStrLn $ show result
process_lines func inh
------------------------------
process_lines_in_file :: (String -> b) -> FilePath -> IO ()
process_lines_in_file func filename =
do inh <- openFile filename ReadMode
process_lines func inh
----------------------------------------
test_csv_row :: String -> Bool
test_csv_row row =
has_empty_string ( get_hashrow ( readCSV row))
----------------------------------------
main :: IO ()
main = do
[filename] <- getArgs
process_lines_in_file test_csv_row filename
return ()
And here’s the error:
Couldn't match expected type `b' against inferred type `IO a'
`b' is a rigid type variable bound by
the type signature for `process_lines' at content-hash-p.hs:29:28
In a stmt of a 'do' expression: result <- func inpStr
In the expression:
do { inpStr <- hGetLine inh;
result <- func inpStr;
putStrLn $ show result;
process_lines func inh }
In the expression:
if ineof then
return ()
else
do { inpStr <- hGetLine inh;
result <- func inpStr;
putStrLn $ show result;
.... }
(In the future, please include the import boilerplate.)
Type inference is not failing — since you’re not asking the compiler to do any type inference! However, type-checking is failing. Let’s see why.
You claim
process_lines :: (String -> b) -> Handle -> IO (). Experienced Haskeller’s will already be shuddering at this type. Why? This type claims that its first argument can be any function at all which does something to aString. But this is an odd claim to make, since the return type of this function doesn’t appear anywhere else in the type ofprocess_lines— meaning, we can call this function, but never use its result! Thanks to laziness, this means that the call will never actually happen.So it’s a weird type. Let’s see if we can take the argument above and find out where it fails in your code; that should help point to the problem.
Take a look at the line marked
HERE. This is the only occurrence offuncin our implementation. According to the argument above, we can never use the output offunc, yet here we seem to be using the output offunc. What type are we using it at? Well, we’re using it at anIO {- something -}type, since it’s in ado-block; furthermore, since we bindresultand then callshow result, the{- something -}must be some type that we can callshowon — that is, a member of theShowclass. So the type offuncis not as unrestricted asString -> b; it’s the more restrictedShow b => String -> IO b. A similar argument applies toprocess_lines_in_file, so that its updated type ought to beprocess_lines_in_file :: Show b => (String -> IO b) -> FilePath -> IO ().(Indeed, if you leave off the type signatures, type inference will infer exactly these types for you.)
Now that
process_lines_in_filedemands a function that doesIO, we can no longer passtest_csv_rowas-is. You can choose either to callprocess_lines_in_file (return . test_csv_row) filenameinmainor to change the implementation oftest_csv_rowto callreturn(which does the trivial IO action: no input or output, just do a pure computation and pretend it did IO).With these changes, the code compiles.