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