Какая польза от основного возврата IO Something, а не IO ()? - PullRequest
4 голосов
/ 20 апреля 2019

Я читаю http://learnyouahaskell.com/ ... И что-то меня удивило:

Из-за этого main всегда имеет сигнатуру типа main :: IO something, где something это какой-то конкретный тип.

?То есть main не обязательно должен быть типа IO(), а может быть IO(String) или IO(Int)?Но какая польза от этого?

Я немного поиграл ...

m@m-X555LJ:~$ cat wtf.hs
main :: IO Int
main = fmap (read :: String -> Int) getLine
m@m-X555LJ:~$ runhaskell wtf.hs
1
m@m-X555LJ:~$ echo $?
0
m@m-X555LJ:~$

Хмм.Итак, моя первая гипотеза опровергнута.Я думал, что это был способ для программы на Haskell вернуть статус завершения в оболочку, подобно тому, как программа на C запускается с int main() и сообщает о состоянии выхода с помощью return 0 или return 1.

Но нет: вышеприведенная программа потребляет 1 от ввода и затем ничего не делает, и, в частности, похоже, не возвращает это 1 в оболочку.

Еще один тест:

m@m-X555LJ:~$ cat wtf.hs
main = getContents
m@m-X555LJ:~$ runhaskell wtf.hs
m@m-X555LJ:~$ 

Wow.На этот раз я попытался вернуть IO String.По неизвестным мне причинам на этот раз Хаскелл не даже не ждет ввода, как это было, когда я возвращал IO Int.Программа, похоже, просто ничего не делает.

Это намекает на то, что значение действительно нигде не возвращается: очевидно, поскольку результаты getContents нигде не используются, вся инструкция была пропущена из-за лени.Но если это было так, почему возврат IO Int не был пропущен?Ну да: я сделал fmap read на действии IO;но, похоже, применимо то же самое, вычисление read необходимо только в том случае, если используется результат действия, который - как намекнул пример main = getContents - не используется, поэтому лень следует также пропустить read иследовательно также getLine, верно?Ну, неправильно - но я запутался, почему.

Какой смысл возвращать IO Something из main, а не только IO ()?

1 Ответ

6 голосов
/ 20 апреля 2019

Это на самом деле несколько вопросов, но по порядку:

  1. «Результат» main не имеет значения, поэтому может быть () или что-нибудь еще.Он вообще не используется.
  2. Типы причин, отличные от IO (), допустимы для main для удобства;В противном случае вам всегда нужно было бы сделать что-то вроде main = void $ realMain, чтобы отменить результаты (вы можете захотеть иметь действие, которое могло бы вернуть результат, который вас не волнует как последнее, что происходит), что немного утомительно.ИМХО, молча отбрасывать вещи - это плохо, и поэтому я бы предпочел, чтобы main был вынужден быть :: IO (), но вы всегда можете получить этот эффект, просто поставив сигнатуру типа самостоятельно, так что на практике это не проблема.
  3. Боковая точка: Если вы хотите выйти с определенным кодом выхода, используйте System.Exit
  4. Причина, по которой fmap read getLine потребляет вывод, а getContents - потому что getContents является ленивым, а getLine - нет, то есть getLine читает строку текста там, где вы думаете, что это происходит, тогда как getContents делает любой фактический ввод-вывод, только если результат "необходим" в мире Haskell;Поскольку результат IO не используется ни для чего, это означает, что он ничего не делает, если getContents - это ваше целое main.
...