Действительно ли Haskell является чисто функциональным языком с учетом unsafePerformIO?

На Haskell обычно ссылаются как на пример чисто функционального языка. Как это может быть оправдано с учетом существования System.IO.Unsafe.unsafePerformIO?

Редактировать: я думал, что под "чисто функциональным" подразумевалось, что невозможно ввести нечистый код в функциональную часть программы.

8 ответов

Решение

Языки, которые мы называем Хаскель

unsafePerformIO является частью спецификации интерфейса внешних функций, а не базовой спецификации Haskell 98. Он может быть использован для создания локальных побочных эффектов, которые не выходят за рамки, чтобы раскрыть чисто функциональный интерфейс. То есть мы используем его, чтобы скрыть эффекты, когда средство проверки типов не может сделать это за нас (в отличие от монады ST, которая скрывает эффекты со статической гарантией).

Чтобы точно проиллюстрировать несколько языков, которые мы называем "Haskell", рассмотрим изображение ниже. Каждое кольцо соответствует определенному набору вычислительных функций, упорядоченных по безопасности, и с областью, соотносимой с выразительной силой (т. Е. Количеством программ, которые вы можете написать, если у вас есть эта функция).

Язык, известный как Haskell 98, указан прямо посередине, допуская полные и частичные функции. Агда (или Эпиграмма), где разрешены только полные функции, еще менее выразительна, но "более чиста" и более безопасна. В то время как Haskell, как мы его используем сегодня, включает в себя все, что есть в FFI, где живет unsafePerformIO. То есть, вы можете написать что-нибудь в современном Haskell, хотя, если вы используете вещи из внешних колец, будет сложнее установить гарантии безопасности, которые будут простыми благодаря внутренним кольцам.

alt text

Таким образом, программы на Haskell, как правило, не построены из 100% ссылочно-прозрачного кода, однако это единственный умеренно распространенный язык, который является чистым по умолчанию.

Я думал, что под "чисто функциональным" подразумевалось, что невозможно ввести нечистый код...

Реальный ответ заключается в том, что unsafePerformIO не является частью Haskell, так же как, скажем, сборщик мусора или система времени выполнения являются частью Haskell. unsafePerformIO есть ли в системе, чтобы люди, которые создают систему, могли создать чистую функциональную абстракцию на очень эффективном оборудовании. У всех реальных языков есть лазейки, которые позволяют сборщикам систем выполнять свои действия более эффективными способами, чем переход на C-код или ассемблерный код.

Что касается более широкой картины того, как побочные эффекты и ввод / вывод вписываются в Haskell через IO Монада, я думаю, что самый простой способ думать о Haskell - это то, что это чистый язык, который описывает эффективные вычисления. Когда описанные вычисления mainсистема времени выполнения добросовестно выполняет эти эффекты.

unsafePerformIO способ получить эффекты небезопасным способом; где "небезопасно" означает "безопасность должна быть гарантирована программистом" - компилятором ничего не проверяется. Если вы опытный программист и готовы выполнять тяжелые обязательства, вы можете использовать unsafePerformIO, Но в этот момент вы больше не программируете на Хаскеле; Вы программируете на небезопасном языке, который очень похож на Haskell.

Язык / реализация является чисто функциональной. Он включает пару "спасательных люков", которые вам не нужно использовать, если вы не хотите.

Я не думаю, что unsafePerformIO означает, что haskell каким-то образом становится нечистым. Вы можете создавать чистые (ссылочно прозрачные) функции из нечистых функций.

Рассмотрим скиплист. Для того, чтобы он работал хорошо, требуется доступ к ГСЧ, нечистой функции, но это не делает структуру данных нечистой. Если вы добавляете элемент, а затем конвертируете его в список, один и тот же список будет возвращаться каждый раз, когда вы добавляете элемент.

По этой причине я думаю, что unsafePerformIO следует рассматривать как обещание PureIO. Функция, которая означает, что функции, которые имеют побочные эффекты и, следовательно, будут помечены системой типов как "нечистые", могут быть признаны ссылочной прозрачностью системой типов.

Я понимаю, что у вас должно быть немного более слабое определение "чистого", чтобы оно сохранялось. т.е. чистые функции являются ссылочно прозрачными и никогда не вызываются из- за побочного эффекта (такого как печать).

К сожалению, язык должен выполнять какую-то реальную работу, а это подразумевает общение с внешней средой.

Хорошо то, что вы можете (и должны) ограничить использование этого кода "вне стиля" несколькими конкретными хорошо документированными частями вашей программы.

У меня есть чувство, что я буду очень непопулярным из-за того, что говорю, что собираюсь сказать, но чувствовал, что должен был ответить на некоторую (на мой взгляд, неправильную) информацию, представленную здесь.

Хотя верно, что unsafePerformIO был официально добавлен к языку как часть приложения FFI, причины этого в значительной степени исторические, а не логические. Он существовал неофициально и широко использовался задолго до того, как Хаскелл когда-либо имел FFI. Официально он никогда не был частью основного стандарта Haskell, потому что, как вы заметили, это было слишком много смущения. Я предполагаю, что надежда заключалась в том, что это как-то исчезнет в будущем. Ну, этого не произошло, и не будет, по моему мнению.

Разработка приложения FFI предоставила удобный повод для unsafePerformIO, чтобы получить представление об официальном стандарте языка, поскольку здесь, вероятно, не так уж и плохо, если сравнивать с добавлением возможности вызывать иностранный (IE C) код (где все ставки сделаны в любом случае относительно статического обеспечения чистоты и безопасности типов). Также было очень удобно разместить его здесь по политическим причинам. Это породило миф о том, что Haskell был бы чист, если бы не все эти грязные "плохо спроектированные" C, или "плохо спроектированные" операционные системы, или "плохо спроектированные" аппаратные средства или... что угодно... Это действительно так unsafePerformIO регулярно используется с кодом, связанным с FFI, но причины этого часто связаны с плохим дизайном FFI и самого Haskell, а не плохим дизайном того, что посторонний пользователь Haskell тоже пытается использовать в интерфейсе.

Таким образом, как говорит Норман Рэмси, официальная позиция состояла в том, что можно было использовать unsafePerformIO при условии, что кто-либо использовал его, чтобы были выполнены определенные обязательства по проверке (в первую очередь это делает недействительными важные преобразования компилятора, такие как встраивание и удаление общих подвыражений), Пока все хорошо, или так можно подумать. Настоящим фактом является то, что эти доказательные обязательства не могут быть выполнены с помощью, вероятно, единственного наиболее распространенного варианта использования unsafePerformIO, который, по моим оценкам, составляет более 50% от всех unsafePerformIO, находящихся на свободе. Я говорю об ужасающей идиоме, известной как "unsafePerformIO hack", которая доказуемо (на самом деле очевидно) совершенно небезопасна (при наличии inlining и cse) .

На самом деле у меня нет времени, пространства или желания разбираться в том, что такое "взлом unsafePerformIO" или зачем он нужен в реальных библиотеках ввода-вывода, но суть в том, что люди, работающие над инфраструктурой ввода-вывода Haskells, обычно "застряли между рок и трудное место ". Они могут либо предоставить по своей сути безопасный API, который не имеет безопасной реализации (в Haskell), либо они могут предоставить по своей сути небезопасный API, который можно безопасно реализовать, но что они редко могут сделать, так это обеспечить безопасность как при разработке, так и при реализации API. Судя по удручающей регулярности, с которой в реальном коде появляется код "unsafePerformIO" (включая стандартные библиотеки Haskell), кажется, что большинство выбирает первый вариант как меньшее из двух зол, и просто надеются, что компилятор не испортит вещи с встраиванием, cse или любой другой трансформации.

Хотелось бы, чтобы все это было не так. К сожалению, это так.

Safe Haskell, недавнее расширение GHC, дает новый ответ на этот вопрос. unsafePerformIO является частью GHC Haskell, но не является частью безопасного диалекта.

unsafePerformIO следует использовать только для построения ссылочно-прозрачных функций; например, памятка. В этих случаях автор пакета помечает его как "заслуживающий доверия". Безопасный модуль может импортировать только безопасные и надежные модули; он не может импортировать небезопасные модули.

Для получения дополнительной информации: руководство GHC, бумага Safe Haskell

На Haskell обычно ссылаются как на пример чисто функционального языка. Как это может быть оправдано с учетом существования System.IO.Unsafe.unsafePerformIO?

Редактировать: я думал, что под "чисто функциональным" подразумевалось, что невозможно ввести нечистый код в функциональную часть программы.

Монада IO фактически определена в Haskell, и вы можете увидеть ее определение здесь. Эта монада существует не для борьбы с примесями, а для устранения побочных эффектов. В любом случае, вы могли бы на самом деле соответствовать своему выходу из IO монада, так что существование unsafePerformIO не должен действительно беспокоить вас.