Переодеть монаду 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 функции, такие как изменяемые ссылки и массивы.

Другие вопросы по тегам