Haskell 中的函数映射到 MonadTrans?
Function in Haskell to map over a MonadTrans?
我最近决定开始使用 monad 转换而不是堆叠我的 monad,因为这似乎是正确的做法。无论如何,我之前并没有真正堆叠很多单子。我得到(我认为)它背后的想法和 lift
函数,据我所知,它作为一种 return
用于转换(将底层 monad 中的某些东西放入转换后的 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 a
和 m (t Identity a)
之间假定的同构是明确的,所以我正在寻找函数:
fromTrans :: t m a -> m (t Identity a)
toTrans :: m (t Identity a) -> t m a
据我了解 monad 转换器,这些函数应该始终存在并且相当简单,对吧?
有了这些我显然可以实现 transmap
和 transreturn
:
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
我确信我忽略了一些明显的东西。请帮我指出来。
谢谢。
从评论中的讨论来看,听起来您真正想要的是用于自定义 monad 的 monad 转换器,然后将其应用于基本 monad State
。换句话说,如果您的自定义 monad 是 "nearly" 列表:
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
没有 monad 转换器,因为简单的替换
newtype BadIOT m a = BadIOT (IO (m a))
正如您指出的那样,这很愚蠢。
编辑:以下所有内容都没有意义。我留下删除线。下面的答案很好。
郑重声明,我的最终解决方案既不使用也不实现 monad 转换器,而是简单地实现以下函数:(我的自定义 monad 称为 EnumProc
):
(..>>=) :: Monad m => EnumProc (m a) -> (a -> m b) -> EnumProc (m b)
en ..>>= f = en <$> (>>= f)
infixl 7 ..>>=
这使我能够在保持外部 monad 结构的同时处理 monad 内部的 monadic 计算。 fmap
就够了,我自己也很惊讶。
然后我使用 EnumProc (State s a)
作为类型。
过了一会儿,我终于想出了我从一开始就在寻找的东西。我可以完全按照我想要的方式使用 StateT
,并且它具有我认为的语义,但我没有很好地解释它(并在我写的地方写错了)。
回到我原来的 post,我不需要 State
作为输入,State
/StateT
monad 已经在 monadic 元素中包含了输入.所以我需要的是一个函数recFuncT :: [t] -> StateT s m a
,它的行为等同于以下非变压器函数:
recFunc :: a -> [t] -> m (State s a)
recFunc x [] = return (return x)
recFunc x (t:ts) = myFunc (recFunc x ts)
可以直接实现,使用构造函数StateT
和runStateT
。在这里:
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
级别完成。
您正在寻找的一个概念似乎可以在 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。
我最近决定开始使用 monad 转换而不是堆叠我的 monad,因为这似乎是正确的做法。无论如何,我之前并没有真正堆叠很多单子。我得到(我认为)它背后的想法和 lift
函数,据我所知,它作为一种 return
用于转换(将底层 monad 中的某些东西放入转换后的 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 a
和 m (t Identity a)
之间假定的同构是明确的,所以我正在寻找函数:
fromTrans :: t m a -> m (t Identity a)
toTrans :: m (t Identity a) -> t m a
据我了解 monad 转换器,这些函数应该始终存在并且相当简单,对吧?
有了这些我显然可以实现 transmap
和 transreturn
:
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
我确信我忽略了一些明显的东西。请帮我指出来。
谢谢。
从评论中的讨论来看,听起来您真正想要的是用于自定义 monad 的 monad 转换器,然后将其应用于基本 monad State
。换句话说,如果您的自定义 monad 是 "nearly" 列表:
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
没有 monad 转换器,因为简单的替换
newtype BadIOT m a = BadIOT (IO (m a))
正如您指出的那样,这很愚蠢。
编辑:以下所有内容都没有意义。我留下删除线。下面的答案很好。
郑重声明,我的最终解决方案既不使用也不实现 monad 转换器,而是简单地实现以下函数:(我的自定义 monad 称为 EnumProc
):
(..>>=) :: Monad m => EnumProc (m a) -> (a -> m b) -> EnumProc (m b)
en ..>>= f = en <$> (>>= f)
infixl 7 ..>>=
这使我能够在保持外部 monad 结构的同时处理 monad 内部的 monadic 计算。 fmap
就够了,我自己也很惊讶。
然后我使用 EnumProc (State s a)
作为类型。
过了一会儿,我终于想出了我从一开始就在寻找的东西。我可以完全按照我想要的方式使用 StateT
,并且它具有我认为的语义,但我没有很好地解释它(并在我写的地方写错了)。
回到我原来的 post,我不需要 State
作为输入,State
/StateT
monad 已经在 monadic 元素中包含了输入.所以我需要的是一个函数recFuncT :: [t] -> StateT s m a
,它的行为等同于以下非变压器函数:
recFunc :: a -> [t] -> m (State s a)
recFunc x [] = return (return x)
recFunc x (t:ts) = myFunc (recFunc x ts)
可以直接实现,使用构造函数StateT
和runStateT
。在这里:
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
级别完成。
您正在寻找的一个概念似乎可以在 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。