将完整的 monadic 动作提升到 transformer(>>= 但对于 Monad Transformers)
Lifting a complete monadic action to a transformer (>>= but for Monad Transformers)
我很难确定这是否可能是一个重复的问题,但找不到任何专门解决这个问题的内容。如果真的有什么,我很抱歉。
所以,我明白了 lift
是如何工作的,它将一个 monadic 动作(完全定义)从最外层的 transformer 提升到转换后的 monad。酷
但是,如果我想将变压器下一层的 (>>=)
应用到变压器中怎么办?我会用一个例子来解释。
说MyTrans
是一个MonadTrans
,还有一个实例Monad m => Monad (MyTrans m)
。现在,此实例中的 (>>=)
将具有此签名:
instance Monad m => Monad (MyTrans m) where
(>>=) :: MyTrans m a -> (a -> MyTrans m b) -> MyTrans m b
但我需要的是这样的:
(>>=!) :: Monad m => MyTrans m a -> (m a -> MyTrans m b) -> MyTrans m b
总的来说:
(>>=!) :: (MonadTrans t, Monad m) => t m a -> (m a -> t m b) -> t m b
它看起来像是原始 (>>=)
和 lift
的组合,但事实并非如此。 lift
只能用于 m a
类型的协变参数,以将它们转换为 t m a
,反之则不行。换句话说,以下是错误的类型:
(>>=!?) :: Monad m => MyTrans m a -> (a -> m b) -> MyTrans m b
x >>=!? f = x >>= (lift . f)
当然,一般的 colift :: (MonadTrans t, Monad m) => t m a -> m a
绝对没有意义,因为变压器肯定在做一些我们不能在所有情况下就这样扔掉的东西。
但是就像 (>>=)
通过确保它们总是 "come back" 将逆变参数引入 monad 一样,我认为 (>>=!)
函数中的一些东西是有意义的:是的,它以某种方式从 t m a
生成 m a
,但这只是因为它在 t
内完成了所有这些操作,就像 (>>=)
从 a
m a
在某种程度上。
我已经考虑过了,我不认为 (>>=!)
可以从可用的工具中进行一般定义。从某种意义上说,它比 MonadTrans
给出的要多。我也没有找到任何提供此功能的相关类型 classes。 MFunctor
是相关的,但它是另一回事,用于更改内部 monad,而不是用于链接专门的 transformer 相关操作。
顺便说一句,这里有一个例子来说明您为什么要这样做:
编辑:我试图举一个简单的例子,但我意识到可以用来自变压器的常规 (>>=)
来解决这个问题。我的真实例子(我认为)不能用这个来解决。如果您认为每个案例都可以用通常的方式解决 (>>=)
,请解释一下如何解决。
我是否应该为此定义自己的类型 class 并提供一些基本实现? (我对 StateT
很感兴趣,而且我几乎可以肯定它可以为它实现)我在做一些扭曲的事情吗?有什么我忽略的吗?
谢谢。
编辑:Fyodor 提供的答案与类型匹配,但不符合我的要求,因为通过使用 pure
,它忽略了 m
monad 的 monadic 效果。这是给出错误答案的示例:
取 t
= StateT Int
和 m
= []
.
x1 :: StateT Int [] Int
x1 = StateT (\s -> [(1,s),(2,s),(3,s)])
x2 :: StateT Int [] Int
x2 = StateT (\s -> [(1,s),(2,s),(3,s),(4,s))])
f :: [Int] -> StateT Int [] Int
f l = StateT (\s -> if (even s) then [] else (if (even (length l)) then (fmap (\z -> (z,z+s)) l) else [(123,123)]))
runStateT (x1 >>= (\a -> f (pure a))) 1
returns [(123,123),(123,123),(123,123)]
正如预期的那样,因为 1
都是奇数并且 x1
中的列表具有奇数长度。
但是 runStateT (x2 >>= (\a -> f (pure a))) 1
returns [(123,123),(123,123),(123,123),(123,123)]
,而我本来希望它是 return [(1,2),(2,3),(3,4),(4,5)]
,因为 1
是奇数列表的长度是偶数。相反,由于 pure
调用,f
的计算独立发生在列表 [(1,1)]
、[(2,1)]
、[(3,1)]
和 [(4,1)]
上。
这可以通过 bind
+ pure
非常简单地实现。考虑签名:
(>>=!) :: (Monad m, MonadTrans t) => t m a -> (m a -> t m a) -> t m a
如果您在第一个参数上使用 bind
,您将得到一个裸体 a
,并且由于 m
是一个 Monad
,您可以轻松地将其变成裸体a
通过 pure
变成 m a
。因此,直接的实现是:
(>>=!) x f = x >>= \a -> f (pure a)
因此,bind
总是比您提议的新操作 (>>=!)
更强大,这可能是它不存在于标准库中的原因。
我认为可以针对某些特定的转换器或特定的底层 monad 提出 (>>=!)
更巧妙的解释。例如,如果 m ~ []
,人们可能会想象将整个列表作为 m a
传递,而不是像我上面的通用实现那样将其元素一个一个地传递。但是这种事情似乎太具体了,无法普遍实施。
如果您有一个非常具体的示例来说明您所追求的目标,并且您可以证明我上面的一般实现不起作用,那么也许我可以提供更好的答案。
好的,根据评论解决您的实际问题:
I have a function f :: m a -> m b -> m c
that I want to transform into a function ff :: StateT s m a -> StateT s m b -> StateT s m c
我认为看这个例子可能会更好地说明难度。考虑所需的签名:
liftish :: Monad m => (m a -> m b -> m c) -> StateT m a -> StateT m b -> StateT m c
据推测,您希望在 StateT m a
和 StateT m b
参数中保留已经 "imprinted" 的 m
的效果(因为如果您不- 我上面的简单解决方案将起作用)。为此,您可以通过 runStateT
"unwrap" StateT
,这将分别获得 m a
和 m b
,然后您可以使用它们来获得 m c
:
liftish f sa sb = do
s <- get
let ma = fst <$> runStateT sa s
mb = fst <$> runStateT sb s
lift $ f ma mb
但问题来了:看到里面的 fst <$>
了吗?他们正在丢弃结果状态。调用 runStateT sa s
不仅会产生 m a
值,还会产生新的修改状态。 runStateT sb s
也是如此。并且您可能想要获得 runStateT sa
产生的状态并将其传递给 runStateT sb
,对吗?否则你实际上是在放弃一些状态突变。
但是您无法到达 runStateT sa
的结果状态,因为它在 m
中是 "wrapped"。因为 runStateT
returns m (a, s)
而不是 (m a, s)
。如果你知道如何 "unwrap" m
,你会很好,但你不知道。因此,获得该中间状态的唯一方法是 运行 m
:
的效果
liftish f sa sb = do
s <- get
(c, s'') <- lift $ do
let ma = runStateT sa s
(_, s') <- ma
let mb = runStateT sb s'
(_, s'') <- mb
c <- f (fst <$> ma) (fst <$> mb)
pure (c, s'')
put s''
pure c
但现在看看会发生什么:我使用了 ma
和 mb
两次:一次是从中获取新状态,第二次是将它们传递给 f
.这可能会导致双重运行宁效应或更糟。
这个 "double execution" 的问题,我认为,会出现在任何 monad transformer 中,只是因为 transformer 的效果总是包裹在底层 monad 中,所以你有一个选择:要么放弃 transformer 的效果,要么执行底层 monad 的效果两次。
我觉得你"really want"是
(>>>==) :: MyTrans m a -> (forall b. m b -> MyTrans n b) -> MyTrans n a
-- (=<<) = flip (>>=) is nicer to think about, because it shows that it's a form of function application
-- so let's think about
(==<<<) :: (forall a. m b -> MyTrans n b) -> (forall a. MyTrans m a -> MyTrans n a)
-- hmm...
type (~>) a b = forall x. a x -> b x
(==<<<) :: (m ~> MyTrans n) -> MyTrans m ~> MyTrans n
-- look familiar?
也就是说,您是在 monad 类别上描述 monad。
class MonadTrans t => MonadMonad t where
-- returnM :: m ~> t m
-- but that's just lift, therefore the MonadTrans t superclass
-- note: input must be a monad homomorphism or else all bets are off
-- output is also a monad homomorphism
(==<<<) :: (Monad m, Monad n) => (m ~> t n) -> t m ~> t n
instance MonadMonad (StateT s) where
-- fairly sure this is lawful
-- EDIT: probably not
f ==<<< StateT x = do
(x, s) <- f <$> x <$> get
x <$ put s
但是,要让您的示例发挥作用是不可能的。太不自然了。 StateT Int []
是非确定性演化状态的程序的 monad。那个 monad 的一个重要 属性 是每个 "parallel universe" 都没有接收到来自其他 monad 的通信。 any 有用的类型类可能不会提供您正在执行的特定操作。你只能做一部分:
f :: [] ~> StateT Int []
f l = StateT \s -> if odd s && even (length l) then fmap (\x -> (x, s)) l else []
f ==<<< x1 = []
f ==<<< x2 = [(1,1),(2,1),(3,1),(4,1)]
我很难确定这是否可能是一个重复的问题,但找不到任何专门解决这个问题的内容。如果真的有什么,我很抱歉。
所以,我明白了 lift
是如何工作的,它将一个 monadic 动作(完全定义)从最外层的 transformer 提升到转换后的 monad。酷
但是,如果我想将变压器下一层的 (>>=)
应用到变压器中怎么办?我会用一个例子来解释。
说MyTrans
是一个MonadTrans
,还有一个实例Monad m => Monad (MyTrans m)
。现在,此实例中的 (>>=)
将具有此签名:
instance Monad m => Monad (MyTrans m) where
(>>=) :: MyTrans m a -> (a -> MyTrans m b) -> MyTrans m b
但我需要的是这样的:
(>>=!) :: Monad m => MyTrans m a -> (m a -> MyTrans m b) -> MyTrans m b
总的来说:
(>>=!) :: (MonadTrans t, Monad m) => t m a -> (m a -> t m b) -> t m b
它看起来像是原始 (>>=)
和 lift
的组合,但事实并非如此。 lift
只能用于 m a
类型的协变参数,以将它们转换为 t m a
,反之则不行。换句话说,以下是错误的类型:
(>>=!?) :: Monad m => MyTrans m a -> (a -> m b) -> MyTrans m b
x >>=!? f = x >>= (lift . f)
当然,一般的 colift :: (MonadTrans t, Monad m) => t m a -> m a
绝对没有意义,因为变压器肯定在做一些我们不能在所有情况下就这样扔掉的东西。
但是就像 (>>=)
通过确保它们总是 "come back" 将逆变参数引入 monad 一样,我认为 (>>=!)
函数中的一些东西是有意义的:是的,它以某种方式从 t m a
生成 m a
,但这只是因为它在 t
内完成了所有这些操作,就像 (>>=)
从 a
m a
在某种程度上。
我已经考虑过了,我不认为 (>>=!)
可以从可用的工具中进行一般定义。从某种意义上说,它比 MonadTrans
给出的要多。我也没有找到任何提供此功能的相关类型 classes。 MFunctor
是相关的,但它是另一回事,用于更改内部 monad,而不是用于链接专门的 transformer 相关操作。
顺便说一句,这里有一个例子来说明您为什么要这样做:
编辑:我试图举一个简单的例子,但我意识到可以用来自变压器的常规 (>>=)
来解决这个问题。我的真实例子(我认为)不能用这个来解决。如果您认为每个案例都可以用通常的方式解决 (>>=)
,请解释一下如何解决。
我是否应该为此定义自己的类型 class 并提供一些基本实现? (我对 StateT
很感兴趣,而且我几乎可以肯定它可以为它实现)我在做一些扭曲的事情吗?有什么我忽略的吗?
谢谢。
编辑:Fyodor 提供的答案与类型匹配,但不符合我的要求,因为通过使用 pure
,它忽略了 m
monad 的 monadic 效果。这是给出错误答案的示例:
取 t
= StateT Int
和 m
= []
.
x1 :: StateT Int [] Int
x1 = StateT (\s -> [(1,s),(2,s),(3,s)])
x2 :: StateT Int [] Int
x2 = StateT (\s -> [(1,s),(2,s),(3,s),(4,s))])
f :: [Int] -> StateT Int [] Int
f l = StateT (\s -> if (even s) then [] else (if (even (length l)) then (fmap (\z -> (z,z+s)) l) else [(123,123)]))
runStateT (x1 >>= (\a -> f (pure a))) 1
returns [(123,123),(123,123),(123,123)]
正如预期的那样,因为 1
都是奇数并且 x1
中的列表具有奇数长度。
但是 runStateT (x2 >>= (\a -> f (pure a))) 1
returns [(123,123),(123,123),(123,123),(123,123)]
,而我本来希望它是 return [(1,2),(2,3),(3,4),(4,5)]
,因为 1
是奇数列表的长度是偶数。相反,由于 pure
调用,f
的计算独立发生在列表 [(1,1)]
、[(2,1)]
、[(3,1)]
和 [(4,1)]
上。
这可以通过 bind
+ pure
非常简单地实现。考虑签名:
(>>=!) :: (Monad m, MonadTrans t) => t m a -> (m a -> t m a) -> t m a
如果您在第一个参数上使用 bind
,您将得到一个裸体 a
,并且由于 m
是一个 Monad
,您可以轻松地将其变成裸体a
通过 pure
变成 m a
。因此,直接的实现是:
(>>=!) x f = x >>= \a -> f (pure a)
因此,bind
总是比您提议的新操作 (>>=!)
更强大,这可能是它不存在于标准库中的原因。
我认为可以针对某些特定的转换器或特定的底层 monad 提出 (>>=!)
更巧妙的解释。例如,如果 m ~ []
,人们可能会想象将整个列表作为 m a
传递,而不是像我上面的通用实现那样将其元素一个一个地传递。但是这种事情似乎太具体了,无法普遍实施。
如果您有一个非常具体的示例来说明您所追求的目标,并且您可以证明我上面的一般实现不起作用,那么也许我可以提供更好的答案。
好的,根据评论解决您的实际问题:
I have a function
f :: m a -> m b -> m c
that I want to transform into a functionff :: StateT s m a -> StateT s m b -> StateT s m c
我认为看这个例子可能会更好地说明难度。考虑所需的签名:
liftish :: Monad m => (m a -> m b -> m c) -> StateT m a -> StateT m b -> StateT m c
据推测,您希望在 StateT m a
和 StateT m b
参数中保留已经 "imprinted" 的 m
的效果(因为如果您不- 我上面的简单解决方案将起作用)。为此,您可以通过 runStateT
"unwrap" StateT
,这将分别获得 m a
和 m b
,然后您可以使用它们来获得 m c
:
liftish f sa sb = do
s <- get
let ma = fst <$> runStateT sa s
mb = fst <$> runStateT sb s
lift $ f ma mb
但问题来了:看到里面的 fst <$>
了吗?他们正在丢弃结果状态。调用 runStateT sa s
不仅会产生 m a
值,还会产生新的修改状态。 runStateT sb s
也是如此。并且您可能想要获得 runStateT sa
产生的状态并将其传递给 runStateT sb
,对吗?否则你实际上是在放弃一些状态突变。
但是您无法到达 runStateT sa
的结果状态,因为它在 m
中是 "wrapped"。因为 runStateT
returns m (a, s)
而不是 (m a, s)
。如果你知道如何 "unwrap" m
,你会很好,但你不知道。因此,获得该中间状态的唯一方法是 运行 m
:
liftish f sa sb = do
s <- get
(c, s'') <- lift $ do
let ma = runStateT sa s
(_, s') <- ma
let mb = runStateT sb s'
(_, s'') <- mb
c <- f (fst <$> ma) (fst <$> mb)
pure (c, s'')
put s''
pure c
但现在看看会发生什么:我使用了 ma
和 mb
两次:一次是从中获取新状态,第二次是将它们传递给 f
.这可能会导致双重运行宁效应或更糟。
这个 "double execution" 的问题,我认为,会出现在任何 monad transformer 中,只是因为 transformer 的效果总是包裹在底层 monad 中,所以你有一个选择:要么放弃 transformer 的效果,要么执行底层 monad 的效果两次。
我觉得你"really want"是
(>>>==) :: MyTrans m a -> (forall b. m b -> MyTrans n b) -> MyTrans n a
-- (=<<) = flip (>>=) is nicer to think about, because it shows that it's a form of function application
-- so let's think about
(==<<<) :: (forall a. m b -> MyTrans n b) -> (forall a. MyTrans m a -> MyTrans n a)
-- hmm...
type (~>) a b = forall x. a x -> b x
(==<<<) :: (m ~> MyTrans n) -> MyTrans m ~> MyTrans n
-- look familiar?
也就是说,您是在 monad 类别上描述 monad。
class MonadTrans t => MonadMonad t where
-- returnM :: m ~> t m
-- but that's just lift, therefore the MonadTrans t superclass
-- note: input must be a monad homomorphism or else all bets are off
-- output is also a monad homomorphism
(==<<<) :: (Monad m, Monad n) => (m ~> t n) -> t m ~> t n
instance MonadMonad (StateT s) where
-- fairly sure this is lawful
-- EDIT: probably not
f ==<<< StateT x = do
(x, s) <- f <$> x <$> get
x <$ put s
但是,要让您的示例发挥作用是不可能的。太不自然了。 StateT Int []
是非确定性演化状态的程序的 monad。那个 monad 的一个重要 属性 是每个 "parallel universe" 都没有接收到来自其他 monad 的通信。 any 有用的类型类可能不会提供您正在执行的特定操作。你只能做一部分:
f :: [] ~> StateT Int []
f l = StateT \s -> if odd s && even (length l) then fmap (\x -> (x, s)) l else []
f ==<<< x1 = []
f ==<<< x2 = [(1,1),(2,1),(3,1),(4,1)]