将完整的 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 Intm = [].

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 aStateT m b 参数中保留已经 "imprinted" 的 m 的效果(因为如果您不- 我上面的简单解决方案将起作用)。为此,您可以通过 runStateT "unwrap" StateT,这将分别获得 m am 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

但现在看看会发生什么:我使用了 mamb 两次:一次是从中获取新状态,第二次是将它们传递给 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)]