Невозможно наблюдать значение указателя внутри модуля ForeignPtr
снаружи модуля Data.ByteString
; его реализация внутренне нечиста, но внешне чиста, потому что она гарантирует, что инварианты, необходимые для чистоты, сохраняются до тех пор, пока вы не видите внутри конструктора ByteString
, который вы не может, потому что он не экспортируется.
Это обычная техника в Haskell: реализация чего-то с небезопасными технологиями под капотом, но выставление чистого интерфейса; вы получаете как производительность, так и мощность небезопасных методов, не ставя под угрозу безопасность Haskell. (Конечно, в модулях реализации могут быть ошибки, но вы думаете, что ByteString
меньше будет давать утечку своей абстракции, если она будет написана на C? :))
Что касается тонкостей, то, если вы говорите с точки зрения пользователя, не беспокойтесь: вы можете использовать любую функцию, экспортируемую библиотеками ByteString и Vector, не беспокоясь, если они не начинаются с unsafe
. Обе они являются очень зрелыми и хорошо протестированными библиотеками, так что вы вообще не должны сталкиваться с какими-либо проблемами чистоты, а если вы сталкиваетесь, это ошибка в библиотеке, и вы должны сообщить об этом.
Что касается написания собственного кода, обеспечивающего внешнюю безопасность с небезопасной внутренней реализацией, правило очень простое: поддерживать ссылочную прозрачность.
Взяв в качестве примера ByteString, функции для создания ByteString используют unsafePerformIO
для выделения блоков данных, которые затем изменяются и помещаются в конструктор. Если бы мы экспортировали конструктор, то пользовательский код смог бы получить доступ к ForeignPtr
. Это проблематично? Чтобы определить, так ли это, нам нужно найти чистую функцию (т. е. не в IO
), которая позволяет нам различать два ForeignPtr, выделенных таким образом. Беглый взгляд на документацию показывает, что есть такая функция: instance Eq (ForeignPtr a)
позволит нам их различать. Поэтому мы не должны позволять коду пользователя получать доступ к ForeignPtr
. Самый простой способ сделать это — не экспортировать конструктор.
Подводя итог: когда вы используете небезопасный механизм для реализации чего-либо, убедитесь, что вносимая им нечистота не может просочиться за пределы модуля, например. путем проверки значений, которые вы производите с ним.
Что касается проблем с компилятором, вам не о чем беспокоиться; хотя функции небезопасны, они не должны позволять вам делать что-то более опасное, кроме нарушения чистоты, чем то, что вы можете сделать в монаде IO
для начала. Как правило, если вы хотите сделать что-то, что может привести к действительно неожиданным результатам, вам придется приложить все усилия, чтобы сделать это: например, вы можете использовать unsafeDupablePerformIO
, если вы можете справиться с возможностью двух потоков, оценивающих один и тот же преобразователь формы unsafeDupablePerformIO m
одновременно. unsafePerformIO
немного медленнее, чем unsafeDupablePerformIO
, потому что предотвращает это. (Преобразователи в вашей программе могут оцениваться двумя потоками одновременно во время нормального выполнения с GHC; обычно это не проблема, так как вычисление одного и того же чистого значения дважды не должно иметь неблагоприятных побочных эффектов (по определению), но при написании небезопасного кода, это то, что вы должны принять во внимание.)
документация GHC для unsafePerformIO
(и unsafeDupablePerformIO
, как я указал выше) подробно описывает некоторые ловушки, с которыми вы можете столкнуться; аналогично документация для unsafeCoerce#
(которое следует использовать через переносимое имя, Unsafe.Coerce.unsafeCoerce).
person
ehird
schedule
23.12.2011