【问题标题】:Lifting a complete monadic action to a transformer (>>= but for Monad Transformers)将完整的单子动作提升到变压器(>>= 但对于单子变压器)
【发布时间】:2019-12-28 02:25:27
【问题描述】:

我很难确定这是否是一个重复的问题,但找不到任何专门解决此问题的内容。如果真的有什么,我很抱歉。

所以,我了解了lift 的工作原理,它将一个单子动作(完全定义)从最外层的转换器提升到转换后的单子。很酷。

但是,如果我想将变压器下一层的(>>=) 应用到变压器中怎么办?我会用一个例子来解释。

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 绝对是零意义,因为肯定变压器正在做一些我们不能在所有情况下都像那样扔掉的东西。

但是就像(>>=) 通过确保它们总是“返回”来将逆变参数引入 monad 一样,我认为类似于 (>>=!) 函数的内容是有道理的:是的,它在某种程度上使m a 来自 t m a,但这仅仅是因为它在 t 内完成了所有这些,就像 (>>=) 在某种程度上从 m a 生成 a

我已经考虑过了,我认为(>>=!) 通常不能从可用工具中定义。从某种意义上说,它比MonadTrans 提供的更多。我也没有找到任何提供此功能的相关类型类。 MFunctor 是相关的,但它是另一回事,用于更改内部 monad,但不适用于仅链接与转换器相关的操作。

顺便说一下,这里有一个示例说明您为什么要这样做:

编辑:我试图提出一个简单的例子,但我意识到可以使用变压器中的常规 (>>=) 来解决这个问题。我的真实例子(我认为)不能用这个来解决。如果您认为每个案例都可以使用通常的(>>=) 解决,请解释一下。

我应该为此定义自己的类型类并提供一些基本实现吗? (我对StateT 很感兴趣,而且我几乎可以肯定它可以实现)我在做一些扭曲的事情吗?有什么我忽略的吗?

谢谢。

编辑:Fyodor 提供的答案与类型匹配,但不符合我的要求,因为使用pure,它忽略了m monad 的单子效应。这是一个给出错误答案的例子:

采取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 按预期返回[(123,123),(123,123),(123,123)],因为1 都是奇数,而x1 中的列表长度是奇数。

但是runStateT (x2 >>= (\a -> f (pure a))) 1 返回[(123,123),(123,123),(123,123),(123,123)],而我预计它会返回[(1,2),(2,3),(3,4),(4,5)],因为1 是奇数并且列表的长度是偶数。相反,由于 pure 调用,f 的评估正在列表 [(1,1)][(2,1)][(3,1)][(4,1)] 上独立进行。

【问题讨论】:

    标签: haskell monads monad-transformers state-monad


    【解决方案1】:

    这可以通过bind + pure 非常简单地实现。考虑签名:

    (>>=!) :: (Monad m, MonadTrans t) => t m a -> (m a -> t m a) -> t m a
    

    如果你在第一个参数上使用bind,你会得到一个赤裸裸的a,因为m是一个Monad,你可以通过@轻松地将赤裸裸的a变成m a 987654330@。因此,直接的实现是:

    (>>=!) x f = x >>= \a -> f (pure a)
    

    因此,bind 总是比您提议的新操作 (>>=!) 更强大,这可能是它在标准库中不存在的原因。


    我认为对于某些特定的转换器或特定的底层 monad,可能会提出更巧妙的 (>>=!) 解释。例如,如果m ~ [],人们可能会想象将整个列表传递为m a,而不是像我上面的通用实现那样逐个传递它的元素。但是这种事情似乎太具体而无法普遍实施。

    如果你有一个非常具体的例子来说明你所追求的,并且你可以证明我上面的一般实现不起作用,那么也许我可以提供一个更好的答案。


    好的,从 cmets 解决您的实际问题:

    我有一个函数f :: m a -> m b -> m c,我想将它转换成一个函数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 参数中保留已经“印记”的 m 的效果(因为如果您不这样做 - 我上面的简单解决方案将起作用)。为此,您可以通过runStateT“解包”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 &lt;$&gt; 了吗?他们正在丢弃结果状态。对runStateT sa s 的调用不仅会产生m a 值,还会产生新的修改状态。 runStateT sb s 也是如此。并且大概您想要获得由runStateT sa 产生的状态并将其传递给runStateT sb,对吗?否则,您实际上是在删除一些状态突变。

    但您无法达到runStateT sa 的结果状态,因为它“包裹”在m 中。因为runStateT 返回m (a, s) 而不是(m a, s)。如果你知道如何“解开”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。这可能会导致双重运行效果或更糟。

    我认为,这种“双重执行”的问题会出现在任何 monad 转换器中,因为转换器的效果总是包裹在底层的 monad 中,所以你有一个选择:要么放弃转换器的效果,要么执行底层的monad 的效果两次。

    【讨论】:

    • 确实签名匹配,这是相关的,因为这意味着在我的特定情况下我正在寻找其他东西,即使我还不知道为什么。但是,实现不是我想要的。通过使用pure,您在某种程度上没有使用函数f 可能查看其参数的单子方面的潜在方式。我在下一条评论中完成的示例。
    • 请将示例放在问题本身中。对此的评论很糟糕。
    • 添加有问题。
    • 你的例子正是我的答案的后半部分。简短的回答是:我认为它太具体而不能普遍有用。如果我有足够的时间,我稍后会尝试详细说明我为什么这么认为。
    • 不,我不能使用rarb 代替fst &lt;$&gt;,因为rarb 的类型错误。我必须使用f (pure ra) (pure rb),在这种情况下,我们马上回到第一个简单的解决方案。
    【解决方案2】:

    我认为你“真正想要”的是

    (>>>==) :: 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 [] 是非确定性地演化状态的程序的单子。该单子的一个重要特性是,每个“平行宇宙”都不会收到来自其他宇宙的通信。 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)]
    

    【讨论】:

    • 我仍在处理这个答案,但你知道。 monad 类别中的 monad 已经存在:hackage.haskell.org/package/mmorph-1.1.3/docs/…MMonad 类)我整天都在看它,想看看它是否适合我。我没有深入研究它的一个原因是StateT 没有预定义的实现,但你只是提供了一个!我写的例子可能行不通,因为正如你所说,它太奇怪了,但它可以很好地解决我实际的实际问题,即使更大,也不会那么复杂。还在想。
    • 哦,哎呀。如果该包没有定义一个实例,那么这可能意味着没有合法的实例。那么这里的那个可能坏了。
    猜你喜欢
    • 2018-12-20
    • 2012-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多