Haskell язык ленивый. Для интерактивных программ достаточно вообще обычных строк:
main = interact realMain
realMain :: String -> String
realMain = unlines . f . lines
where
f x = "Введите своё имя: ":g x
g (имя:числа) = ["Привет, " ++ имя, "Вводите числа, q для выхода"] ++ корни числа
корни ("q":_) = []
корни (x:xs) = (show . sqrt) (read x :: Double):корни xs
Вот программа, которая запрашивает имя пользователя, затем выводит корни введённых чисел пока не получит q. Никакого IO внутри realMain не требуется.
Если брать произвольное взаимодействие с окружающим миром, то достаточно main :: [inEvent] -> [outEvent]
.
Зачем нужен «магический» тип IO, делящий Haskell на фактически два разных языка?