Каков наилучший способ поддержки монадных преобразователей в функциях, которые принимают монадические команды в качестве аргументов? - PullRequest
0 голосов
/ 16 февраля 2012

Допустим, у меня есть вычисление

class A ? where
    foo :: ? () -> ? ()

instance A IO where
    foo x = do
        print "prefix"
        x
        print "suffix"

Теперь, предположим, я хочу написать

instance A ? => A (MyMonadTransformerT γ ?)

Затем, при реализации foo, я вынужден "развернуть" его аргумент, например, foo x = lift (foo (unlift x)). Эта unlift функция может быть плохой для монадических вычислений. Для преобразователя состояния он будет вынужден забыть о любых изменениях состояния программы.

Кажется, работает над созданием более общего метода, который также принимает функцию подъема и приводит к вычислению t () -> t (), где t - это поднятая (преобразованная) монада.

class Monad ? => A' ? where
    foo' :: Monad t =>
        (forall z . ? z -> t z) -- lifting function
        -> t ()
        -> t ()
    foo :: ? () -> ? ()
    foo = foo' id

instance A' IO where
    foo' lift x = do
        lift (print "prefix")
        x
        lift (print "suffix")

instance A' ? => A' (StateT γ ?) where
    foo' lift' x = foo' (lift' . lift) x

computation :: Num a => StateT a IO ()
computation = do
    foo (put 1 >> lift (print "middle"))
    v <- get
    lift $ print ("value", v)

run_computation :: Num a => IO a
run_computation = execStateT computation 0

Вопрос . Это лучший способ? Можно ли написать что-то более общее? Код в стиле CPS? Спасибо !!

1 Ответ

2 голосов
/ 16 февраля 2012

Прежде всего, забудьте, что class бизнес, похоже, вы просто хотите функцию.

Эта проблема решается с помощью классов Monad*: MonadIO, MonadState и т. Д. Поэтому, если у вас есть монадическое вычисление, которое может выполнять IO, но которое разрешено делать другие вещи, вы должны принять как введите параметр m любая монада, которая может выполнять действия ввода-вывода:

foo :: (MonadIO m) => m () -> m ()
foo x = do
    liftIO $ putStrLn "prefix"
    x
    liftIO $ putStrLn "suffix"

Теперь не имеет значения, что такое m, потому что MonadIO говорит, как вернуть его к нужным операциям.

Классы Monad* несколько немодулярны перед лицом новых преобразователей - количество экземпляров, которое вам нужно, является квадратичным по числу преобразователей монад. Существуют различные неоптимальные решения этой проблемы. Если такие вещи касаются вас, вы всегда можете повторно определить класс:

foo :: (Monad m) => (forall a. IO a -> m a) -> m () -> m ()
foo lift x = do
    lift $ putStrLn "prefix"
    x
    lift $ putStrLn "suffix"

Будет ли это делать, зависит от вашего уровня абстракции. Вы захотите первое, если вы пишете библиотеку, на которой строится код контента, и, возможно, второе, если вы пишете библиотеку, на которой строите другой код библиотеки. В любом случае, это немного сложно, потому что стеки монад не коммутируют.

...