Инициализация библиотеки unsafePerformIO и FFI
Я создаю модуль FFI для библиотеки в C, которая хочет, чтобы единовременная не реентерабельная функция вызывалась раньше, чем что-либо еще. Этот вызов идемпотентен, но с состоянием, так что я мог бы просто вызывать его при каждом вызове на Haskell. Но это медленно и из-за невступления может вызвать конфликты.
Так что это подходящее время для использования unsafePerformIO? Я мог бы обернуть Bool в небезопасный IORef или MVar, чтобы сделать эти вызовы инициализации идемпотентными, игнорируя последующие вызовы (вызовы, в которых глобальное скрытое состояние IORef равно False).
Если нет, как правильно это сделать?
2 ответа
Я предпочитаю подход, состоящий в том, чтобы выполнить однократную инициализацию и предоставить не поддаваемый токен в качестве доказательства того, что вы инициализировали машину.
Таким образом, ваши доказательства будут:
data Token = Token
которые вы экспортируете абстрактно.
Тогда ваша функция инициализации может вернуть это свидетельство.
init :: IO Token
Теперь вам нужно передать это доказательство вашему API:
bar :: Token -> IO Int
bar !tok = c_call_bar
и т.п.
Теперь вы можете обернуть все это монадой или некоторой средой инициализации более высокого порядка, чтобы сделать ее чище, но это основная идея.
Проблема с инициализацией библиотек C с использованием скрытого состояния заключается в том, что вы в конечном итоге либо не можете распараллелить доступ к библиотеке, либо у вас возникают проблемы в GHCi, смешивании скомпилированного и байт-кода с двумя разными версиями загруженной библиотеки C (что не получится при ошибка компоновщика).
Я хотел бы отметить, что в настоящее время предлагается новый трюк для / вместо withSocketsDo
Нил Митчелл, основанный на evaluate
("Приводит к тому, что его аргумент оценивается как нормальная форма слабой головы при выполнении результирующего действия ввода-вывода."):
withSocketsDo act = do evaluate withSocketsInit; act
{-# NOINLINE withSocketsInit #-}
withSocketsInit = unsafePerformIO $ do
initWinsock
termWinsock
Мой подход к устранению требования вызывать withSocketsDo состоял в том, чтобы сделать его очень дешевым, а затем разбрасывать его везде, где это может понадобиться.
Не обязательно это прекрасная идея...
(См. Также его ответ, объявляющий об этом обновлении в библиотеке.)