Почему тип "Main.main", "IO ()", а не "IO a"?
Почему тип Main.main
должен быть IO ()
и не IO String
или же IO Int
или же IO whatever
?
main :: IO ([] Char) -- Type error
main = (>>) ((>>=) getLine putStrLn) getLine
3 ответа
Если вы читаете IO a
как " IO
вычисление, которое производит a
"Тогда возникает вопрос о том, что среда выполнения делает с a
тот main
производит. Это может просто уронить это. Это потребует такой функции, как
void :: IO a -> IO () -- defined more generally in Control.Monad
а потом дайте нам
realMain :: IO ()
realMain = void main
С этим нет особых проблем, кроме документации. Если я попрошу вас о значении IO a
тогда остается некоторая неуверенность в том, что я мог бы сделать с этим a
, Если я прошу значение IO ()
тогда эта неопределенность уменьшается: я могу создавать ценности ()
Сам, когда захочу, твоя мне не нужна, поэтому я должен заботиться только о побочных эффектах бега IO
вычисление.
Это лучше соответствует фактическому использованию main
поэтому для большей ясности типа среда выполнения Haskell может попросить main :: IO ()
,
Это также заставляет создателя main
быть явным о том, что они делают с любыми "возвращаемыми значениями", которые они создают. Если у меня есть mainish :: IO a
тогда мне нужно явно void
это перед передачей его во время выполнения.
Тем не менее, как указывает Сиби, GHC фактически принимает main :: IO a
и молча отбрасывает a
Таким образом, вопрос немного спорный. Смотрите вступление к Главе 5 Отчета на Haskell.
Тип main
может быть IO a
, Это проверки типов:
main :: IO Int
main = do
putStrLn "test"
return 2
И тогда вы можете выполнить это самостоятельно:
$ ghc -o test test.hs
[1 of 1] Compiling Main ( test.hs, test.o )
Linking test ...
$ ./test
test
main
это функция, с которой программа начинает выполняться. Так что main
функция с типом IO a
не имеет особого смысла, потому что никакая другая функция не будет фактически использовать a
завернут в IO
,
Вы на самом деле можете иметь main
быть какой-то тип IO T
где T
не ()
,
Я думаю, это плохо, что это не ошибка, хотя. Там нет причин для main
быть любым другим типом IO ()
и это может привести к очень тонким проблемам во время выполнения, как
main = putStrLn <$> getContents
У меня была такая проблема, мне потребовалось время, чтобы разобраться. Конечно, этого можно избежать, всегда предоставляя сигнатуру типа для всех определений верхнего уровня (что должен делать каждый), но я все еще чувствую, что нет причины разрешать какой-либо тип ввода-вывода для main
Кроме как IO ()
,
(Проблема с приведенным выше кодом заключается в том, что main
фактически оценивается как действие ввода-вывода, содержащее действие ввода-вывода, а не фактически является действием ввода-вывода. То есть тип main :: IO (IO ())
, Правильный код будет либо main = join $ putStrLn <$> getContents
или же main = putStrLn =<< getContents
).