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

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

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

8 ответов


Языки, Которые Мы Называем Haskell

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

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

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

alt text

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


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

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

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

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


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


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

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

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

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


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

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


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

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

дополнительные сведения: С GHC руководство, безопасная бумага Haskell


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

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

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

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

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

Я хочу, чтобы все это было не так. К сожалению, это так.


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

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

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