Вот сценарий: дана библиотека C, с некоторой структурой в ее ядре и операциями над ней, обеспеченной обилием функций C.
Шаг 1: Используя Haskell's FFI , создается обертка.Он имеет такие функции, как myCLibInit :: IO MyCLibObj
, myCLibOp1 :: MyCLibObj -> ... -> IO ()
и так далее.MyCLibObj
- это непрозрачный тип, который переносит (и скрывает) Ptr
или ForeignPtr
в фактическую структуру C, например, как показано в wiki или в RWH ch.17 .
Шаг 2: Использование unsafeIOToST
из Control.Monad.ST.Unsafe
преобразовать все действия IO
в ST
действия.Для этого нужно ввести что-то вроде
data STMyCLib s = STMyCLib MyCLibObj
, а затем обернуть все функции IO
в функции ST
, например:
myCLibInit' :: ST s (STMyCLib s)
myCLibInit' = unsafeIOToST $ STMyCLib <$> myCLibInit
Это позволяет писать программы в императивном стиле.которые отражают использование OO-подобной библиотеки C, например:
doSomething :: ST s Bool
doSomething = do
obj1 <- myCLibInit'
success1 <- myCLibOp1' obj1 "some-other-input"
...
obj2 <- myCLibInit'
result <- myCLibOp2' obj2 42
...
return True -- or False
main :: IO ()
main = do
...
let success = runST doSomething
...
Шаг 3: Зачастую не имеет смысла смешивать операции над несколькими MyCLibObj
за один раз-блок.Например, когда структура C является (или должна рассматриваться как) одноэлементным экземпляром.Делать что-то, как в doSomething
выше, либо бессмысленно, либо просто запрещено (например, когда структура C является static
).В этом случае необходим язык, напоминающий монаду State
:
doSomething :: ResultType
doSomething = withMyCLibInstance $ do
success <- myCLibOp1'' "some-other-input"
result <- myCLibOp2'' 42
...
return result
где
withMyCLibInstance :: Q a -> a
И это приводит к вопросу : Как можно переодеть монаду ST s a
в нечто похожее на монаду State
.Так как withMyCLibInstance
будет использовать функцию runST
, новая монада, назовем ее Q
(для 'q'uestion), должна быть
newtype Q a = Q (forall s. ST s a)
Это выглядит для меня совершенно странно.Я уже борюсь с реализацией экземпляра Functor
для этого Q
, не говоря уже о Applicative
и Monad
.ST s
на самом деле уже является монадой, но состояние s
не должно выходить за пределы монады ST
, следовательно, forall s. ST s a
.Это единственный способ избавиться от s
, потому что runST :: (forall s. ST s a) -> a
, а withMyCLibInstance
- это просто myCLibInit'
, за которым следует runST
.Но почему-то это не подходит.
Как правильно решить шаг 3?Должен ли я даже сделать шаг 2 или бросить Q
сразу после шага 1?Я чувствую, что это должно быть довольно просто.В монаде ST
есть все, что мне нужно, просто нужно правильно настроить Q
...
Обновление 1: Примеры синглтона и статической структуры в шаге 3не очень хорошоЕсли два таких блока do выполняются параллельно, могут произойти очень плохие вещи, то есть оба блока do будут работать параллельно на одной и той же структуре C.