将计算从State monad提升到RWS monad

Are*_* Fu 2 monads state haskell writer reader

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

newtype Problem a = Problem { unProblem :: RWS MyEnv MyLog MyState a }
                    deriving ({- lots of typeclasses -})
Run Code Online (Sandbox Code Playgroud)

通过组装表单的基本计算逐步构建计算

foo :: a -> Problem b
Run Code Online (Sandbox Code Playgroud)

然而,有时候,子计算不需要RWSmonad 的全部功能.例如,考虑一下

bar :: c -> State MyState d
Run Code Online (Sandbox Code Playgroud)

我想barProblemmonad 的上下文中使用作为更大计算的一部分.我可以看到三种方式,这对我来说似乎都不是很优雅.

  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
    
    Run Code Online (Sandbox Code Playgroud)
  2. bar通过将其提升到Problemmonad 中来修改类型签名.这有一个缺点,即新类型签名没有明确承诺bar独立于MyEnv并且不记录任何内容MyLog.

  3. RWS显式ReaderT MyEnv WriterT MyLog State MyStatemonad堆栈替换monad.这使我能够简洁地lift.lift将子bar计算转换为完整的monad; 但是,这个技巧不适用于例如表格的子计算c -> Reader MyEnv d.

有没有组成一个更清洁的方式foobar?我有一种预感,一些聪明的类型类实例定义可能会成功,但我无法确切地知道如何继续.

Ale*_*lec 7

我假设您正在使用mtl(如果您不这样做,请考虑这样做 - 这些库大部分是兼容的,除了后面的内容).你可以得到的情况下MonadReader MyEnv,MonadWriter 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)
Run Code Online (Sandbox Code Playgroud)

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

bar :: MonadState MyState m => c -> m d
bar = ...
Run Code Online (Sandbox Code Playgroud)

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

foo :: (MonadState MyState m, MonadReader MyEnv m, MonadWriter MyLog m) => a -> m b
foo = ...
Run Code Online (Sandbox Code Playgroud)

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

baz :: Problem ()
baz = foo 2 >> bar "hi" >> return ()
Run Code Online (Sandbox Code Playgroud)

现在,为什么这一般有用?它归结为你的monad"堆栈"的灵活性.如果明天你决定,你实际上并不需要RWS,但只有State你可以重构Problem相应的和bar(只曾经需要摆在首位状态)将继续无需任何修改工作.

一般情况下,我尽量避免使用WriterT,StateT,RWST等里面像辅助功能foobar.选择在那里让你的代码,一般和实现无关尽可能使用类型类MonadWriter,MonadState,MonadReader等.然后,你只需要使用WriterT,StateT,RWST一旦你的代码中:当你真正"跑"的单子.

关于 transformers

如果您正在使用transformers,这一切都无效.这不一定是坏事:mtl由于总是能够"找到"组件(如州或作家)有一些问题.