更一般地说,您要做的是将转换应用到转换器堆栈的内层。对于两个任意 monad,类型签名可能如下所示:
fmapMT :: (MonadTrans t, Monad m1, Monad m2) => (m1 a -> m2 a) -> t m1 a -> t m2 a
基本上是更高级别的fmap。事实上,将它与最终参数的映射结合起来可能更有意义:
fmapMT :: (MonadTrans t, Monad m1, Monad m2) => (m1 a -> m2 b) -> t m1 a -> t m2 b
显然,这在所有情况下都不可能,尽管当“源”monad 是 Identity 时可能会更容易,但我可以想象为它工作的地方定义另一个类型类。我认为典型的 monad 转换器库中没有这样的东西。然而,一些关于 hackage 的浏览会发现一些非常相似的东西 in the Monatron package:
class MonadT t => FMonadT t where
tmap' :: FunctorD m -> FunctorD n -> (a -> b)
-> (forall x. m x -> n x) -> t m a -> t n b
tmap :: (FMonadT t, Functor m, Functor n) => (forall b. m b -> n b)
-> t m a -> t n a
tmap = tmap' functor functor id
在tmap' 的签名中,FunctorD 类型基本上是fmap 的临时实现,而不是直接使用Functor 实例。
此外,对于两个类似 Functor 的类型构造函数 F 和 G,具有类似 (forall a. F a -> G a) 类型的函数描述了从 F 到 G 的 a natural transformation。很可能在 @ 987654334@ 包,但我不确定 monad 转换器的类别理论版本是什么,所以我不知道它可能被称为什么。
由于tmap 只需要一个Functor 实例(任何Monad 必须具有)和一个自然变换,并且任何Monad 都具有来自return 提供的Identity monad 的自然变换,因此您想要的函数可以针对FMonadT 的任何实例一般地编写为tmap (return . runIdentity)——假设“基本”monad 被定义为应用于Identity 的转换器的同义词,无论如何,这通常是变压器库。
回到您的具体示例,请注意,Monatron 确实有一个 FMonadT 实例@ 用于 StateT。