【问题标题】:Function in Haskell to map over a MonadTrans?Haskell 中映射 MonadTrans 的函数?
【发布时间】:2019-12-19 10:25:17
【问题描述】:

我最近决定开始使用 monad 转换而不是堆叠我的 monad,因为这似乎是正确的做法。无论如何,我之前并没有真正堆叠很多单子。我得到(我认为)它背后的想法和 lift 函数,据我所知,它充当了转换的一种 return (将一些来自底层 monad 的东西放入转换后的 monad)。

到目前为止一切都很好,但我没有看到任何类似于 fmap 用于单子转换的函数。让我给你举个例子。假设我有一个自定义 monad,m,并且我在其上使用了 StateT 转换,因此使用类型 StateT s m a 而不是 m (State s a)

现在,碰巧在我的 monad m 中,我有一个函数可以转换 monadic 元素(实际上它是 monad 的构造函数之一,如果您需要我可以提供的详细信息),同时保留一些感知潜在价值:myFunc :: m a -> m a

所以我正在构建一个递归函数recFunc :: State s a -> [t] -> m (State s a),看起来类似于这样:

recFunc :: State s a -> [t] -> m (State s a)
recFunc x [] = return x
recFunc x (t:ts) = myFunc (recFunc x ts)

但是如果我尝试使用 monad 转换来复制它,我会遇到问题,因为我找不到将 myFunc 插入到混合中的方法。将输入写为State s a 还是StateT s Identity a 都没有关系(代数上哪个更精确?)

recFuncT :: StateT s Identity a -> [t] -> StateT s m a
recFuncT x [] = ???
recFuncT x (t:ts) = ????? where rec = recFuncT x ts

所以我正在寻找类似(发明,如果可能的话我不知道如何实现)以下功能:

transmap :: (MonadTrans t, Monad m) => (forall b. m b -> m b) -> t m a -> t m a
transmap = ???

transreturn :: (MonadTrans t, Monad m) => m (t Identity a) -> t m a
transreturn = ???

我觉得我应该能够使用 lift 定义这些,但老实说,我不知道如何。

如果我有它们,那么我可以这样做:

recFuncT :: StateT s Identity a -> [t] -> StateT s m a
recFuncT x [] = transreturn (return x)
recFuncT x (t:ts) = transmap myFunc (recFuncT x ts)

也许我真正想要的是更基本的东西。我希望t m am (t Identity a) 之间假定的同构是明确的,所以我正在寻找函数:

fromTrans :: t m a -> m (t Identity a)
toTrans :: m (t Identity a) -> t m a

据我了解 monad 转换器,这些函数应该始终存在并且相当简单,对吧?

有了这些,我显然可以实现transmaptransreturn

transmap :: (MonadTrans t, Monad m) => (forall b. m b -> m b) -> t m a -> t m a
transmap f x = toTrans (f (fromTrans x))

transreturn :: (MonadTrans t, Monad m) => m (t Identity a) -> t m a
transreturn = toTrans

我确信我忽略了一些明显的东西。请为我指出来。

谢谢。

【问题讨论】:

  • State s a == StateT s Identity a,所以StateT s m am (StateT s Identity a) 是两个不同的东西。一个与m (s -> (a, s)) 同构,另一个与s -> m (a, s) 同构。
  • @chepner 这是一个公平的观点,但即使这是真的,它仍然没有解决我的高级问题,即:我假设如果一个 monad 有一个关联的转换器,那么任何你可以通过堆叠单子来做的事情,你可以通过使用变压器来做,称之为“变压器原理”。所以我想要做的是,你可以通过堆叠 monad 来做到这一点,正如我的实现 recFunc 所展示的那样。因此,如果原理成立(并且由于State monad 确实有一个转换器),那么必须有一种方法可以使用转换器来做到这一点。那是什么?
  • 是的,但是您的转换不仅仅是堆叠单子。 recFuncTrecFunc不同。如果相同,则根本不需要更改正文,因为只需将 State 替换为等效的 StateT 只是别名扩展。
  • @chepner 我很确定你一定没有抓住重点。您始终可以将State 替换为StateT,但并非StateT 的所有实例都可以直接替换为State 的实例。也就是说,只要m 不是身份,那么StateT s m a 就不能用State 重写。然而,从概念上和我理解的变形金刚,StateT s m a 是“在m 中插入State 功能”,因此至少应该具有m (State s a) 的所有功能。你是说这不是真的,有些事情你只能用m (State s a) 做吗?
  • 我认为问题在于StateT s m a可以被视为“在m中插入State功能”,但m (State s a)类型不能。甚至不清楚m (State s a) 是什么,至少对于一般的monad mIO (State s a) 是什么?如果它应该是一个 monad,它的绑定运算符是什么样的?如果它不应该是一个单子,它的“能力”是什么?

标签: haskell monads


【解决方案1】:

根据 cmets 中的讨论,听起来您真正想要的是自定义 monad 的 monad 转换器,然后将其应用于基本 monad State。换句话说,如果您的自定义 monad “几乎”是一个列表:

newtype Listish a = Listish [a]

它的转换器版本将具有类型:

newtype ListishT m a = ListishT [m a]

所以你最终的 monad 转换器堆栈将是:

type M s = ListishT (State s)

与你的 monad 堆栈同构

[State s a]  AKA  Listish (State s a)

不过,请确保不要过度概括从底层 monad 创建转换器的模式。而一些单子的变形金刚:

newtype List a = List [a]
newtype Reader r a = Reader (r -> a)

通过将“a”替换为“m a”来合理推导出:

newtype ListT m a = ListT [m a]
newtype ReaderT r m a = ReaderT (r -> m a)

其他类型的变压器的派生方式不同。例如:

newtype State s a = State (s -> (a, s))
newtype Writer w a = Writer (a, w)

给:

newtype StateT s a = StateT (s -> m (a, s))
-- **NOT** StateT (s -> (m a, s))
newtype WriterT s a = WriterT (m (a, w))
-- **NOT** WriterT (m a, w)

特别是IO 没有单子转换器,因为简单的替换

newtype BadIOT m a = BadIOT (IO (m a))

正如你所指出的那样,很愚蠢。

【讨论】:

  • 嗯。但是你可以为任何单子定义它,这是没有意义的。为什么这个单子更有意义?你可以实现:newtype TransformerFromMonad tm m a = TransformerFromMonad (tm (m a)) 然后考虑像TransformerFromMonad IO (State s) == IO (State s a) 这样可怕的东西,这与IO monad 的转换器完全不同。
  • 我为此添加了一条注释。
  • 也许我试图过多地概括 monad 转换器,而不是将它们单独用于它们每个是什么......
【解决方案2】:

您正在寻找的一个概念似乎可以在mmorph 包中找到:

class MFunctor t where
  -- The argument is generally required to be a monad morphism,
  -- but some instances will work sensibly when it's not.
  hoist :: Monad m => (forall x. m x -> n x) -> t m a -> t n a

这比你的版本更通用一点,因为它允许替换底层的 monad。

【讨论】:

  • 它甚至有一个预定义的实例MFunctor (StateT s),这正是我想要的。这正是我最初寻找的答案。
【解决方案3】:

编辑:以下所有内容都毫无意义。我留下删除线。下面的答案很好。

为了记录,我的最终解决方案既不是使用也不是实现 monad 转换器,而是简单地实现以下功能:(我的自定义 monad 称为EnumProc):

(..>>=) :: Monad m => EnumProc (m a) -> (a -> m b) -> EnumProc (m b)
en ..>>= f = en <$> (>>= f)
infixl 7 ..>>=

这让我可以在我的 monad 内部处理 monadic 计算,同时保持外部 monad 结构。当fmap 足够时,我自己都感到惊讶。

然后我使用EnumProc (State s a) 作为整个类型。

【讨论】:

    【解决方案4】:

    过了一会儿,我终于想出了我从一开始就在寻找的东西。我可以完全按照我想要的方式使用StateT,它具有我认为的语义,但是我没有很好地解释它(并且在我写的内容中加入了错误)。

    回到我原来的帖子,我不需要State 作为输入,State/StateT monad 已经在 monadic 元素中包含了输入。所以我需要的是一个函数recFuncT :: [t] -&gt; StateT s m a,它的行为等同于以下非变压器:

    recFunc :: a -> [t] -> m (State s a)
    recFunc x [] = return (return x)
    recFunc x (t:ts) = myFunc (recFunc x ts)
    

    可以直接实现,使用构造函数StateTrunStateT。这里是:

    recFuncT :: a -> [t] -> StateT m s a
    recFuncT x [] = return x
    recFuncT x (t:ts) = StateT (\s -> myFunc (runStateT (recFuncT x ts) s))
    

    此外,transmap 的功能也可以实现一般的,至少对于StateT

    transmap :: Monad m => (forall b. m b -> m b) -> StateT s m a -> StateT s m a
    transmap f st = StateT (\s -> f (runStateT st s)
    

    然后我们可以用它很好地写recFuncT

    recFuncT :: a -> [t] -> StateT m s a
    recFuncT x [] = return x
    recFuncT x (t:ts) = transmap myFunc (recFuncT x ts)
    

    我意识到这与我最初包含的代码并不真正匹配,但它确实符合我试图诉诸的总体原则,即StateT 转换器就像向我的 monad m 添加状态一样,因此可以在m (State s a) 级别完成的任何事情都可以在StateT s m a 级别完成。

    【讨论】:

    • 由于我的低代表,它不允许我将这个设置为最终接受。我意识到我已经多次改变主意并改变了问题的措辞,但据我所知,当我不是我所谈论的专家时,这很正常。我不知道 Stack Overflow 的确切政策是什么,试图让它变得一团糟。如果我需要编辑某些内容、合并某些内容、删除某些内容或其他任何内容,请告诉我,我会尽力去做。再次感谢那些帮助我深入了解它的人。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-22
    • 1970-01-01
    • 2020-05-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多