如何使免费的 monad 解释器递归?

How to make a free monad interpreter recursive?

在使用 iterTFree monad interpreter 中,我想要一个内部状态,但我不确定该怎么做,因为 iterT 函数提供了延续f 预加载了递归调用,据我所知。我想 StateT 包装器是一个可能的解决方案(?),但如果可能的话最好避免。谢谢。

编辑:澄清一下,内部状态是传递给 inner 函数的 mval 参数。我想分配一个资源并继续解释该资源。

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

正如我在评论中指出的那样,乍一看这似乎是 iterTM 的工作,就像 iterT 除了它在您选择的 monad 转换器中运行。

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

您可以选择输出 monad t,但是 ma 是由您折叠起来的 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 ())

我接受本杰明的答案是正确的,因为它可能是对这个问题最简单/最好的答案,但我最终发现 FT monad 并没有促进我的用例容易地。然而,operationalmonad-skeleton 确实允许这种用例 quite easily,因为它们不通过绑定函数线程化解释器。与后者一起工作也很有趣。