如何结合数据组合和 monad 转换器
How to combine data composition and monad transformers
我对 monad 转换器有些陌生,目前正在尝试在项目中使用 StateT/Except
堆栈。我遇到的困难是我有几层数据组合(对它们进行操作的类型,包含在对它们进行其他操作的类型中),我不知道如何在该设计中优雅地使用 monad 转换器.具体来说,我在编写以下代码时遇到了问题(显然是简化示例):
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Control.Monad.Except
import Control.Monad.State
import Control.Monad.Trans.Except (Except, throwE)
import Control.Monad.Trans.State (StateT)
data ComposedState = ComposedState { state :: Bool }
data MyError = MyError { message :: String }
-- If the passed in state is true, change it to false; otherwise throw.
throwingModification :: ComposedState -> Except MyError ComposedState
throwingModification (ComposedState True) = return $ ComposedState False
throwingModification _ = throwE $ MyError "error!"
-- A state which composes with @ComposedState@,
data MyState = MyState { composed :: ComposedState }
-- and a monad transformer state to allow me to modify it and propagate
-- errors.
newtype MyMonad a = MyMonad { contents :: StateT MyState (Except MyError) a }
deriving ( Functor
, Applicative
, Monad
, MonadState MyState
, MonadError MyError )
anAction :: MyMonad ()
anAction = do -- want to apply throwingModification to the `composed` member,
-- propogating any exception
undefined
我在 ComposedState
上有一个潜在的 "throwing" 操作,我想在 MyState
上以有状态的抛出操作使用该操作。我显然可以通过解构整个堆栈并重建它来做到这一点,但 monadic 结构的全部意义在于我不应该这样做。是否有简洁、惯用的解决方案?
对于冗长的代码片段深表歉意——我已尽力将其缩减。
最好的解决方案是将 throwingModification
重写为 MyMonad
。
throwingModification :: MyMonad ()
throwingModification = do
s <- get
if state s then
put $ ComposedState False
else
throwError $ MyError "error!"
如果你不能重写你的函数(因为它在别处使用),你可以将它包装起来。
更自然的做法是在 MyMonad
monad 中从头开始写 throwingModification
,如下所示:
throwingModification' :: MyMonad ()
throwingModification' = do ComposedState flag <- gets composed
if not flag then throwError $ MyError "error!"
else modify (\s -> s { composed = (composed s)
{ Main.state = False } })
我在这里假设组合状态包含您想要保留的其他组件,这使得 modify
子句难看。使用镜头可以使它更清洁。
但是,如果您坚持使用 throwingModification
的当前形式,您可能必须编写自己的组合器,因为通常的状态组合器不包括用于切换状态类型的机制 s
,这就是您有效地尝试做的事情。
以下 usingState
的定义可能会有所帮助。它使用 getter 和 setter 将 StateT
操作从一种状态转换为另一种状态。 (同样,镜头方法会更干净。)
usingState :: (Monad m) => (s -> t) -> (s -> t -> s)
-> StateT t m a -> StateT s m a
usingState getter setter mt = do
s <- get
StateT . const $ do (a, t) <- runStateT mt (getter s)
return (a, setter s t)
我不认为有一种简单的方法可以修改 usingState
以在一般 MonadState
monad 之间工作而不是直接在 StateT
上工作,所以你需要提升它手动并通过您的 MyMonad
数据类型进行转换。
有了usingState
这么定义,就可以写成下面这样了。 (注意 >=>
来自 Control.Monad
。)
MyMonad $ usingState getComposed putComposed $
StateT (throwingModification >=> return . ((),))
有帮手:
getComposed = composed
putComposed s c = s { composed = c }
这还是有点难看,不过那是因为类型t -> Except e t
必须适配为StateT (t -> Except e ((), t))
,然后通过combinator转化为s
状态,然后手动包裹在您的 MyMonad
中,如上所述。
有镜片
我并不是说镜头是灵丹妙药或什么的,但它们确实有助于清理代码中一些较丑陋的部分。
添加镜头后:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import Control.Monad ((>=>))
import Control.Monad.Except (Except, MonadError, throwError)
import Control.Monad.State (get, MonadState, runStateT, StateT(..))
data MyError = MyError { _message :: String }
data MyState = MyState { _composed :: ComposedState }
data ComposedState = ComposedState { _state :: Bool }
makeLenses ''ComposedState
makeLenses ''MyError
makeLenses ''MyState
throwingModification
的定义看起来更简洁:
throwingModification :: ComposedState -> Except MyError ComposedState
throwingModification s =
if s^.state then return $ s&state .~ False
else throwError $ MyError "error!"
和我上面给出的MyMonad
版本肯定有好处:
throwingModification' :: MyMonad ()
throwingModification' = do
flag <- use (composed.state)
if flag then composed.state .= False
else throwError (MyError "error!")
usingStateL
的定义看起来没什么不同:
usingStateL :: (Monad m) => Lens' s t -> StateT t m a -> StateT s m a
usingStateL tPart mt = do
s <- get
StateT . const $ do (a, t) <- runStateT mt (s^.tPart)
return (a, s&tPart .~ t)
但它允许使用现有镜头 composed
代替辅助函数:
MyMonad $ usingStateL composed $
StateT (throwingModification >=> return . ((),))
如果你有复杂的嵌套状态,它会概括为 (composed.underneath.state4)
。
我对 monad 转换器有些陌生,目前正在尝试在项目中使用 StateT/Except
堆栈。我遇到的困难是我有几层数据组合(对它们进行操作的类型,包含在对它们进行其他操作的类型中),我不知道如何在该设计中优雅地使用 monad 转换器.具体来说,我在编写以下代码时遇到了问题(显然是简化示例):
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Control.Monad.Except
import Control.Monad.State
import Control.Monad.Trans.Except (Except, throwE)
import Control.Monad.Trans.State (StateT)
data ComposedState = ComposedState { state :: Bool }
data MyError = MyError { message :: String }
-- If the passed in state is true, change it to false; otherwise throw.
throwingModification :: ComposedState -> Except MyError ComposedState
throwingModification (ComposedState True) = return $ ComposedState False
throwingModification _ = throwE $ MyError "error!"
-- A state which composes with @ComposedState@,
data MyState = MyState { composed :: ComposedState }
-- and a monad transformer state to allow me to modify it and propagate
-- errors.
newtype MyMonad a = MyMonad { contents :: StateT MyState (Except MyError) a }
deriving ( Functor
, Applicative
, Monad
, MonadState MyState
, MonadError MyError )
anAction :: MyMonad ()
anAction = do -- want to apply throwingModification to the `composed` member,
-- propogating any exception
undefined
我在 ComposedState
上有一个潜在的 "throwing" 操作,我想在 MyState
上以有状态的抛出操作使用该操作。我显然可以通过解构整个堆栈并重建它来做到这一点,但 monadic 结构的全部意义在于我不应该这样做。是否有简洁、惯用的解决方案?
对于冗长的代码片段深表歉意——我已尽力将其缩减。
最好的解决方案是将 throwingModification
重写为 MyMonad
。
throwingModification :: MyMonad ()
throwingModification = do
s <- get
if state s then
put $ ComposedState False
else
throwError $ MyError "error!"
如果你不能重写你的函数(因为它在别处使用),你可以将它包装起来。
更自然的做法是在 MyMonad
monad 中从头开始写 throwingModification
,如下所示:
throwingModification' :: MyMonad ()
throwingModification' = do ComposedState flag <- gets composed
if not flag then throwError $ MyError "error!"
else modify (\s -> s { composed = (composed s)
{ Main.state = False } })
我在这里假设组合状态包含您想要保留的其他组件,这使得 modify
子句难看。使用镜头可以使它更清洁。
但是,如果您坚持使用 throwingModification
的当前形式,您可能必须编写自己的组合器,因为通常的状态组合器不包括用于切换状态类型的机制 s
,这就是您有效地尝试做的事情。
以下 usingState
的定义可能会有所帮助。它使用 getter 和 setter 将 StateT
操作从一种状态转换为另一种状态。 (同样,镜头方法会更干净。)
usingState :: (Monad m) => (s -> t) -> (s -> t -> s)
-> StateT t m a -> StateT s m a
usingState getter setter mt = do
s <- get
StateT . const $ do (a, t) <- runStateT mt (getter s)
return (a, setter s t)
我不认为有一种简单的方法可以修改 usingState
以在一般 MonadState
monad 之间工作而不是直接在 StateT
上工作,所以你需要提升它手动并通过您的 MyMonad
数据类型进行转换。
有了usingState
这么定义,就可以写成下面这样了。 (注意 >=>
来自 Control.Monad
。)
MyMonad $ usingState getComposed putComposed $
StateT (throwingModification >=> return . ((),))
有帮手:
getComposed = composed
putComposed s c = s { composed = c }
这还是有点难看,不过那是因为类型t -> Except e t
必须适配为StateT (t -> Except e ((), t))
,然后通过combinator转化为s
状态,然后手动包裹在您的 MyMonad
中,如上所述。
有镜片
我并不是说镜头是灵丹妙药或什么的,但它们确实有助于清理代码中一些较丑陋的部分。
添加镜头后:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import Control.Monad ((>=>))
import Control.Monad.Except (Except, MonadError, throwError)
import Control.Monad.State (get, MonadState, runStateT, StateT(..))
data MyError = MyError { _message :: String }
data MyState = MyState { _composed :: ComposedState }
data ComposedState = ComposedState { _state :: Bool }
makeLenses ''ComposedState
makeLenses ''MyError
makeLenses ''MyState
throwingModification
的定义看起来更简洁:
throwingModification :: ComposedState -> Except MyError ComposedState
throwingModification s =
if s^.state then return $ s&state .~ False
else throwError $ MyError "error!"
和我上面给出的MyMonad
版本肯定有好处:
throwingModification' :: MyMonad ()
throwingModification' = do
flag <- use (composed.state)
if flag then composed.state .= False
else throwError (MyError "error!")
usingStateL
的定义看起来没什么不同:
usingStateL :: (Monad m) => Lens' s t -> StateT t m a -> StateT s m a
usingStateL tPart mt = do
s <- get
StateT . const $ do (a, t) <- runStateT mt (s^.tPart)
return (a, s&tPart .~ t)
但它允许使用现有镜头 composed
代替辅助函数:
MyMonad $ usingStateL composed $
StateT (throwingModification >=> return . ((),))
如果你有复杂的嵌套状态,它会概括为 (composed.underneath.state4)
。