【问题标题】:Lifting a computation from the State monad into the RWS monad将计算从 State monad 提升到 RWS monad
【发布时间】:2017-01-04 20:53:42
【问题描述】:

我围绕RWS (Reader+Writer+State) monad 的使用构建了一个计算:

newtype Problem a = Problem { unProblem :: RWS MyEnv MyLog MyState a }
                    deriving ({- lots of typeclasses -})

计算是通过组装形式的基本计算逐步构建的

foo :: a -> Problem b

然而,有时子计算不需要RWS monad 的全部功能。例如,考虑

bar :: c -> State MyState d

我想在Problem monad 的上下文中使用bar 作为更大计算的一部分。我可以看到三种方法来做到这一点,但对我来说,没有一种是很优雅的。

  1. 手动解包 State 计算并将其重新包装在 RWS monad 中:

    baz :: a -> RWS MyEnv MyLog MyState c
    baz x = do temp <- foo x
               initialState <- get
               let (finalResult, finalState) = runState (bar temp) initialState
               put finalState
               return finalResult
    
  2. 修改bar 的类型签名,将其提升到Problem monad。这样做的缺点是新类型签名没有明确承诺 bar 独立于 MyEnv 并且不向 MyLog 记录任何内容。

  3. RWS monad 替换为显式的 ReaderT MyEnv WriterT MyLog State MyState monad 堆栈。这让我可以简洁地将lift.liftbar 子计算成完整的单子;但是,这个技巧将不起作用,例如对于c -&gt; Reader MyEnv d 形式的子计算。

有没有更简洁的方式来编写foobar?我有一种预感,类型类实例的一些巧妙定义可能会解决问题,但我不知道该怎么做。

【问题讨论】:

  • 您使用的是变压器包中的 RWS 还是 mtl 中的 RWS?
  • 我正在使用mtl。我其实不知道transformers

标签: haskell state monads writer reader


【解决方案1】:

我假设您使用的是mtl(如果您不是,请考虑这样做 - 这些库大多是兼容的,除了以下内容)。您可以派生MonadReader MyEnvMonadWriter MyLogMonadState MyState 的实例。然后,您可以使用这些在具有这些约束的任何 monad 堆栈上泛化您的函数。

{-# LANGUAGE GeneralizedNewtypeDeriving, MultiParamTypeClasses, FlexibleContexts #-}

import Control.Monad.RWS
import Control.Monad.Reader
import Control.Monad.Writer
import Control.Monad.State

newtype Problem a = Problem { unProblem :: RWS MyEnv MyLog MyState a }
                    deriving (Functor, Applicative, Monad,
                              MonadReader MyEnv, MonadWriter MyLog, MonadState MyState)

从你的例子来看,可能bar只需要知道MyState有一些状态,就可以给它签名

bar :: MonadState MyState m => c -> m d
bar = ...

然后,即使对于可能需要完整的RWS 功能的foo,您也可以编写

foo :: (MonadState MyState m, MonadReader MyEnv m, MonadWriter MyLog m) => a -> m b
foo = ...

然后,您可以根据自己的喜好混合搭配:

baz :: Problem ()
baz = foo 2 >> bar "hi" >> return ()

现在,为什么这通常很有用?它归结为你的单子“堆栈”的灵活性。如果明天您决定实际上不需要RWS 而只需要State,那么您可以相应地重构Problem,并且bar(最初只需要状态)将继续工作而无需任何更改。

一般来说,我尽量避免在foobar 等辅助函数中使用WriterTStateTRWST 等。选择使用类型类MonadWriterMonadStateMonadReader 等使您的代码尽可能通用且独立于实现。然后,您只需使用WriterTStateTRWST 一次在您的代码中:当您实际“运行”您的 monad 时。

关于transformers的旁注

如果您使用的是transformers,则这些都不起作用。这不一定是坏事:mtl,因为总是能够“找到”组件(如状态或写入器)has some problems

【讨论】:

  • 很好的答案,谢谢。最后的链接非常相​​关。
  • 最后一个链接非常有趣。我有一个(可能是幼稚的)印象,即 Monad 堆栈只会让代码更容易在不同的应用程序中重用。
  • @Undreren 您可能还对该文章的续集感兴趣:123。也就是说,我自己从未在现实生活中遇到过mtl 的限制。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-08-27
  • 1970-01-01
  • 2023-03-29
  • 1970-01-01
  • 2019-05-19
  • 1970-01-01
  • 2014-02-18
相关资源
最近更新 更多