Почему действия getArgs и getProgName IO?
Я полный новичок, который в настоящее время пытается научиться Haskell с помощью Узнать вас о Haskell для Great Good ". Я дошел до раздел, объясняющий, как работать с аргументами командной строки, и что-то меня раздражает.
Из моего понимания (и определение haskell.org), действия предназначены для инкапсуляции побочных эффектов. Аргументы командной строки являются неизменяемыми входами для данного экземпляра программы, тогда в чем смысл getProgName :: IO String
, а не getProgName :: String
? Иными словами, какой смысл запретить чистую функцию вызывать getProgName
?
Обновление
У меня были некоторые отличные ответы на этот вопрос. Я принимаю Дон Стюарт как самый простой и лаконичный, но Конал (с его ассоциированным сообщение в блоге), безусловно, стоит прочитать.
Ответы
Ответ 1
Во-первых, getArgs
может меняться во время выполнения. См. withArgs
.
Во-вторых, getArgs
и getProgName
попадают в интересный класс нечистых вычислений - они считаются константами во время прогона программы, однако они не являются значениями, доступными во время компиляции, и они меняются от одного программа запускается в другую. У них нет четкого обозначения.
См. ранее обсуждения, где обсуждаются getArgs и вычисления с плавающей запятой. И даже minBound/maxBound может считаться в этом классе.
Ответ 2
Чтобы ответить на такие вопросы, вам понадобится фундамент:
что означает, что выражение e
имеет тип t
?
Вы можете дать произвольные ответы для примитивов типа getProgName
, но вам этого не нужно.
Вместо этого рассмотрите значения выражений и типов.
Скажем, что выражение e
имеет тип t
, когда значение, обозначенное e
(т.е. Значение e
как математическое значение), принадлежит коллекции, обозначенной знаком t
.
Теперь рассмотрим t == String
.
Какой смысл мы хотим иметь String
?
Опять же, здесь есть много места для выбора.
Тем не менее, есть большая заслуга в выборе полезного кандидата с простейшим математическим определением.
В Haskell String == [Char]
, поэтому мы действительно говорим о значениях []
и Char
Наиболее убедительные простые кандидаты, о которых я знаю, заключаются в том, что []
обозначает списки (последовательности), а Char
обозначает символы, и, следовательно, String
обозначает последовательности символов, т.е. "Строки".
С выбором, что String
означает строки, теперь мы можем спросить, возможно ли, что getProgName :: String
.
Если это так, то значение getProgName
должно быть последовательностью символов.
Тем не менее, нет единственной последовательности символов, которая фиксирует намерение позади getProgName
.
(Edit: Предположительно, вы хотите, чтобы getProgName
выдавал разные строки, когда он отображается в программах с разными именами.)
Поэтому мы должны либо выбрать другой тип, например (но не обязательно) IO String
, либо выбрать более сложное значение для String
.
Последний маршрут может показаться на первый взгляд привлекательным, пока вы не рассмотрите глубинные последствия.
Чисто функциональное (более точное "денотативное" ) программирование поддерживает практические, строгие рассуждения благодаря использованию простых значений, таких как последовательности, а не сложные значения, такие как функции из какой-либо среды, включая операционную систему, контекст выполнения компьютера.
Для близких замечаний и обсуждений см. сообщение в блоге Понятия о чистоте в Haskell.
Редактирование: я думаю, что Питер Ландин (дед Хаскелла) лучше всего выразил свое определение "денотативного программирования" (или "подлинно функционального программирования" ), которое он рекомендовал в качестве существенной замены неопределенным терминам, таким как "декларативный" и "декларативный", функционального "программирования.
Для справки, цитат и кратких комментариев см. этот комментарий в сообщении Является ли Haskell чисто функциональным языком?.
Ответ 3
Мое понимание чистых функций чиста, потому что вы можете рассуждать с ними в "время кода", и компилятор может во время компиляции. Действия ввода-вывода зависят от времени выполнения. getArgs
, который дает аргументы программе только тогда, когда она запущена, является, следовательно, действием IO.
Как узнать, что Haskell ставит:
Вы можете думать о действии ввода-вывода как о коробке с маленькими ногами, которые пойдут выйти в реальный мир и сделать что-то там (например, написать граффити на стене) и, возможно, вернуть некоторые данные.
Мы не знаем, что коробка с маленькими ногами принесет в кодовое время или время компиляции.