Переодеть монаду ST как нечто похожее на государственную монаду - PullRequest
0 голосов
/ 03 июня 2018

Вот сценарий: дана библиотека 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.

1 Ответ

0 голосов
/ 03 июня 2018

Вы можете использовать эффект считывателя для доступа к одиночному файлу, создав его экземпляр только в функции 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 функциям, таким как изменяемые ссылки и массивы.

...