【问题标题】:MonadFix instance for interpreter monad transformer generated by FreeT?FreeT生成的解释器monad转换器的MonadFix实例?
【发布时间】:2015-05-23 14:10:15
【问题描述】:

我有一个由FreeT生成的标准解释器monad转换器的简化版本:

data InteractiveF p r a = Interact p (r -> a)

type Interactive p r = FreeT (InteractiveF p r)

p 是“提示”,r 是“环境”...可以使用类似的方式运行它:

runInteractive :: Monad m => (p -> m r) -> Interactive p r m a -> m a
runInteractive prompt iact = do
  ran <- runFreeT iact
  case ran of
    Pure x -> return x
    Free (Interact p f) -> do
      response <- prompt p
      runInteractive prompt (f resp)

instance MonadFix m => MonadFix (FreeT (InteractiveF p r)) m a)
mfix = -- ???

我觉得这种类型或多或少只是StateT 的受限版本...如果有的话,Interactive p r IOIO 的受限版本...我认为...但是。 ..好吧,无论如何,我的直觉说应该有一个很好的例子。

我试过写一个,但我似乎无法弄清楚。到目前为止,我最接近的尝试是:

mfix f = FreeT (mfix (runFreeT . f . breakdown))
  where
    breakdown :: FreeF (InteractiveF p r) a (FreeT (InteractiveF p r) m a) -> a
    breakdown (Pure x) = x
    breakdown (Free (Interact p r)) = -- ...?

我还尝试使用利用mMonadFix 实例的版本,但也没有运气--

mfix f = FreeT $ do
  rec ran <- runFreeT (f z)
      z   <- case ran of
               Pure x -> return x
               Free iact -> -- ...
  return -- ...

任何人都知道这是否真的有可能,或者为什么不可能?如果是,有什么好地方让我继续寻找?


另外,在我的实际应用中,我什至不需要使用FreeT...我可以使用Free;也就是说,让Interactive 只是一个 monad 而不仅仅是一个 monad 转换器,并且有

runInteractive :: Monad m => (p -> m r) -> Interactive p r a -> m a
runInteractive _ (Pure x) = return x
runInteractive prompt (Free (Interact p f) = do
    response <- prompt p
    runInteractive prompt (f response)

如果这个案例有可能,而不是一般的 FreeT 案例,我也会很高兴 :)

【问题讨论】:

标签: haskell monads free-monad monadfix


【解决方案1】:

假设您已经有了Interactive 的解释器。

interpret :: FreeT (InteractiveF p r) m a -> m a
interpret = undefined

写一个MonadFix 实例很简单:

instance MonadFix m => MonadFix (FreeT (InteractiveF p r) m) where
    mfix = lift . mfix . (interpret .)

我们可以直接捕捉到这种“了解解释者”的想法,而无需提前指定解释器。

{-# LANGUAGE RankNTypes #-}

data UnFreeT t m a = UnFree {runUnFreeT :: (forall x. t m x -> m x) -> t m a}
--   given an interpreter from `t m` to `m` ^                          |
--                                  we have a value in `t m` of type a ^

UnFreeT 只是一个读取解释器的ReaderT

如果t 是一个monad 转换器,那么UnFreeT t 也是一个monad 转换器。我们可以轻松地从不需要知道解释器的计算中构建UnFreeT,只需忽略解释器即可。

unfree :: t m a -> UnFreeT t m a
--unfree = UnFree . const
unfree x = UnFree $ \_ -> x

instance (MonadTrans t) => MonadTrans (UnFreeT t) where
    lift = unfree . lift

如果t是一个单子变压器,m是一个Monad,并且t m也是一个Monad,那么UnFree t m是一个Monad。给定一个解释器,我们可以将两个需要解释器的计算绑定在一起。

{-# LANGUAGE FlexibleContexts #-}

refree :: (forall x. t m x -> m x) -> UnFreeT t m a -> t m a
-- refree = flip runUnFreeT
refree interpreter x = runUnFreeT x interpreter

instance (MonadTrans t, Monad m, Monad (t m)) => Monad (UnFreeT t m) where
    return = lift . return
    x >>= k = UnFree $ \interpreter -> runUnFreeT x interpreter >>= refree interpreter . k

最后,给定解释器,只要底层 monad 有 MonadFix 实例,我们就可以修复计算。

instance (MonadTrans t, MonadFix m, Monad (t m)) => MonadFix (UnFreeT t m) where
    mfix f = UnFree $ \interpreter -> lift . mfix $ interpreter . refree interpreter . f

一旦我们有了解释器,我们实际上可以做任何底层 monad 可以做的事情。这是因为,一旦我们有了interpreter :: forall x. t m x -&gt; m x,我们就可以执行以下所有操作。我们可以从m x 一直到t m x 一直到UnFreeT t m x 然后再返回。

                      forall x.
lift               ::           m x ->         t m x
unfree             ::         t m x -> UnFreeT t m x
refree interpreter :: UnFreeT t m x ->         t m x
interpreter        ::         t m x ->           m x

用法

对于您的Interactive,您需要将FreeT 包装在UnFreeT 中。

type Interactive p r = UnFreeT (FreeT (InteractiveF p r))

您的解释器仍会被编写为生成FreeT (InteractiveF p r) m a -&gt; m a。将新的Interactive p r m a 一直解释为您将使用的m a

interpreter . refree interpreter

UnFreeT 不再“尽可能地释放解释器”。解释器不再可以随意决定在任何地方做什么。 UnFreeT 中的计算可以请求解释器。当计算请求并使用解释器时,将使用与开始解释程序时相同的解释器来解释程序的那部分。

【讨论】:

    【解决方案2】:

    无法编写MonadFix m =&gt; MonadFix (Interactive p r) 实例。

    您的InteractiveF 是经过充分研究的Moore machines 的基本函子。摩尔机器提供输出,在您的情况下为提示,然后根据输入(在您的情况下为环境)确定下一步要做的事情。摩尔机总是先输出。

    data MooreF a b next = MooreF b (a -> next)
        deriving (Functor)
    

    如果我们遵循C. A. McCann's argumentFree 编写MonadFix 实例,但将自己限制在Free (MooreF a b) 的特定情况下,我们最终将确定如果有MonadFix 实例用于Free (MooreF a b),那么必须存在一个函数

    mooreFfix :: (next -> MooreF a b next) -> MooreF a b next
    

    要编写这个函数,我们必须构造一个MooreF b (f :: a -&gt; next)。我们没有任何bs 可以输出。可以想象,如果我们已经有了下一个a,我们可以获得b,但摩尔机器总是先输出。

    Like in State

    如果您只阅读前面的a,您可以写接近mooreFfix 的内容。

    almostMooreFfix :: (next -> MooreF a b next) -> a -> MooreF a b next
    almostMooreFfix f a = let (MooreF b g) = f (g a)
                          in (MooreF b g)
    

    那么f 必须能够独立于参数next 确定g。所有可能使用的fs 都采用f next = MooreF (f' next) g' 的形式,其中f'g' 是其他一些函数。

    almostMooreFFix :: (next -> b) -> (a -> next) -> a -> MooreF a b next
    almostMooreFFix f' g' a = let (MooreF b g) = f (g a)
                              in (MooreF b g)
                              where
                                  f next = MooreF (f' next) g'
    

    通过一些等式推理,我们可以在 let 的右侧替换 f

    almostMooreFFix :: (next -> b) -> (a -> next) -> a -> MooreF a b next
    almostMooreFFix f' g' a = let (MooreF b g) = MooreF (f' (g a)) g'
                              in (MooreF b g)
    

    我们将g 绑定到g'

    almostMooreFFix :: (next -> b) -> (a -> next) -> a -> MooreF a b next
    almostMooreFFix f' g' a = let (MooreF b _) = MooreF (f' (g' a)) g'
                              in (MooreF b g')
    

    当我们将b 绑定到f' (g' a) 时,let 变得不必要,并且函数没有递归结。

    almostMooreFFix :: (next -> b) -> (a -> next) -> a -> MooreF a b next
    almostMooreFFix f' g' a = MooreF (f' (g' a)) g'
    

    所有不是undefinedalmostMooreFFixes 甚至都不需要let

    【讨论】:

    • 我并不完全相信这个论点......在haskell中,我们有懒惰......我们有MonadFix for State,所以我们a -&gt; s -&gt; (a, s)s -&gt; (a, s)......例如,如果您的函数输出一个恒定的 b 并且不依赖于 next...其余的取决于我们用前两个生成的next...懒惰意味着我们不需要 all next 立即 来获得 一些 b...
    • @JustinL。我们没有任何bs,我们没有任何as,我们也没有任何nexts。如果我们有一个next ,我们可以得到一个b。在需要返回next 之前,我们可以获得a。我们返回的b 不可能用那个a 来定义;如果是这样,我们就会在函数的纯度上戳一个洞;应用 MooreF 构造函数中返回的函数将更改同一构造函数中返回的 b 的值。
    • @JustinL。至于“b 是恒定的,不依赖于next”,moreFFix 必须写成forall b.,这就是为什么我说,“我们没有任何bs”。我们知道b 唯一可以代替函数返回值的值是undefined,这比无用还糟糕。
    • “没有任何bs”并不总能阻止我们使用haskell; fix :: (a -&gt; a) -&gt; a 有效,尽管我们没有任何 as; fix f = f (fix f)
    • @JustinL。这是因为您删除了编写MonadFix 实例所需的IO 部分。 fixIO 基于读取尚未编写的引用的能力。它是根据MVars 和unsafeInterleaveIO 实现的。 Free 是“尽可能地释放解释器”,这阻止了我们做其他一些事情。我相信如果我们承诺一个具体的解释器,我们可以写一个MonadFix 实例。我将发布另一个关于如何在不提交具体解释器的情况下将 MonadFix 添加到 Free 的答案。
    猜你喜欢
    • 2011-07-18
    • 2017-08-04
    • 1970-01-01
    • 2014-11-06
    • 2016-11-05
    • 2018-07-08
    • 1970-01-01
    • 1970-01-01
    • 2012-06-19
    相关资源
    最近更新 更多