Importing Data.Char causes compiler behaviour to change
I'm learning Haskell, and as an exercise I was asked to write a function nextlet
which returns the next letter after the specified parameter (a character). The specification says to assume that 'a' follows 'z' and 'A' follows 'Z', and I made an executive decision that invoking the function on a character outside the ranges A to Z and a to z returns ' '.
My first attempt was:
nextlet c | c == 'Z' = 'A'
| c == 'z' = 'a'
| c `elem` ['A'..'Y'] = succ c
| c `elem` ['a'..'y'] = succ c
| otherwise = ' '
and this worked.
However, I then thought it would be nice not to have to duplicate the processing for upper and lower case, so I found out how to convert a character to upper case, and coded:
nextlet c | c == 'Z' = 'A'
| c `elem` ['A'..'Y'] = succ c
| c `elem` ['a'..'z'] = nextlet toUpper c
| otherwise = ' '
This failed to compile with the error message "Variable not in scope: toUpper :: Char"; which made perfect sense because I hadn't imported Data.Char
.
But when I included import Data.Char
at the top of my script, it still failed to compile. The very first line of the function definition fails with
Couldn't match type ‘Char’ with ‘Char -> Char’
Expected type: (Char -> Char) -> Char -> Char
Actual type: Char -> Char
nextlet c | c == 'Z' = 'A'
^^^^^^^^^^^^^^^^^^^^^^^^^^
What is wrong with this, and why does importing Data.Char
make the line fail to compile when it succeeded before?
Solution 1:
You should use nextlet (toUpper c)
, otherwise you pass toUpper
as parameter, which is a function Char -> Char
, not a Char
.
You thus implement this as:
nextlet c | c == 'Z' = 'A'
| c `elem` ['A'..'Y'] = succ c
| c `elem` ['a'..'z'] = nextlet (toUpper c)
| otherwise = ' '
Solution 2:
Willem Van Onsem's answer explains how to fix your problem, but not why the compiler's behavior seemed to change. GHC has multiple passes. The two important ones in this case are the renamer and the typechecker. When you use a name that can't be resolved, the renamer fails and compilation stops, so you won't see any type errors even if you have them. When you made the name resolve by importing Data.Char
, you revealed a type error that was there all along.