Переодеть монаду ST как нечто похожее на государственную монаду
Вот сценарий: дана библиотека C с некоторой структурой в ее ядре и операциями над ней, обеспеченной обилием функций C.
Шаг 1: Используя FFI Haskell, создается обертка. Имеет такие функции, как myCLibInit :: IO MyCLibObj
, myCLibOp1 :: MyCLibObj -> ... -> IO ()
, и так далее. MyCLibObj
является непрозрачным типом, который несет (и скрывает) Ptr
или же ForeignPtr
к фактической структуре C, например, как показано в этой вики или в RWH гл. 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.
1 ответ
Вы можете использовать эффект чтения для доступа к одиночному файлу, создавая его только в run
функция:
newtype MyCLibST s a = MyCLibST { unMyCLibST :: ReaderT (STMyCLib s) (ST s) a }
runMyCLibST :: (forall s. MyCLibST s a) -> a
runMyCLibST m = runST (myCLibInit >>= runReaderT (unMyCLibST m))
-- Wrap the API with this.
unsafeMkMyCLibST :: (MyCLibObj -> IO a) -> MyCLibST s a
s
должен появиться в качестве параметра MyCLibST
если вы хотите сохранить доступ к другим ST
функции, такие как изменяемые ссылки и массивы.