Как сделать бесплатный интерпретатор монад рекурсивным?

В бесплатном интерпретаторе монад используя iterT, я хотел бы иметь внутреннее состояние, но я не уверен, как это сделать, так как функция iterT предоставляет продолжение, f предварительно загруженное рекурсивным вызовом, насколько я понимаю. Я предполагаю, что обертка StateT является возможным решением (?), но было бы неплохо избежать, если это возможно. Спасибо.

Изменить: чтобы уточнить, внутреннее состояние — это параметр mval, переданный функции inner. Я хотел бы выделить ресурс и продолжить работу интерпретатора с этим ресурсом.

import Control.Monad.Trans.Free.Church
import Control.Monad.Free.TH

type SomeFree m = FT SomeFreeF m
data SomeFreeF next = SomeAct (() -> next)
deriving instance (Functor SomeFreeF)
makeFree ''SomeFreeF

runSomeFree :: SomeFree IO () -> IO ()
runSomeFree = inner Nothing
  where
  inner mval =
    iterT \case
      SomeAct f -> do
        case mval of
          Nothing -> do
            a <- init
            inner (Just a) (FT someAct f ??)
                       -- How to continue the inner loop with    
                       -- the new state and the continuation `f`?
          Just a -> do
            f a

person Scott    schedule 18.06.2020    source источник


Ответы (2)


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

iterTM :: (Functor f, Monad m, MonadTrans t, Monad (t m)) => (f (t m a) -> t m a) -> FreeT f m a -> t m a
iterTM f (FreeT m) = do  -- running in the output monad `t`
    val <- lift m
    case fmap (iterTM f) val of  -- fold the children first
        Pure x -> return x
        Free y -> f y

Вы можете выбрать выходную монаду t, но m и a определяются структурой данных FreeT, которую вы складываете. Для каждого слоя FreeT, начиная с нижнего, iterTM передает f результатов свертки дочерних элементов слоя в ваш обратный вызов. Вы сами решаете, как обрабатывать эти монадические результаты (выбирать между ними, упорядочивать их и т. д.).

Вы упомянули, что запускаете свою складку в StateT, но приведенный вами пример кода больше похож на ReaderT для меня. (Вы не возвращаете измененное состояние с каждой итерации — просто передаете измененный параметр вниз.)

runSomeFree :: Monad m => SomeFree m a -> ReaderT (Maybe ()) m a
runSomeFree = iterTM go
    where
        go (SomeAct f) = ask >>= \case
            Just () -> f ()
            Nothing -> local (const $ Just ()) (f ())
person Benjamin Hodgson♦    schedule 18.06.2020

Я принимаю ответ Бенджамина как правильный, поскольку это, пожалуй, самый простой/лучший ответ на вопрос, но в итоге я обнаружил, что монада FT не облегчила мой вариант использования. Однако operational и monad-skeleton допускают этот вариант использования довольно просто, так как они не передают интерпретатору функцию связывания. С последним также весело и весело работать.

person Scott    schedule 18.06.2020