Haskell -- 使用 StateT monad 转换器链接两个状态
Haskell -- Chaining two states using StateT monad transformers
我在一个 Haskell 应用程序中要跟踪两个或多个独立状态。
我正在使用
声明两个新类型 类
type MonadTuple m = MonadState (Int, Int) m
type MonadBool m = MonadState Bool m
monad 转换器堆栈声明为
type Stack = StateT (Int, Int) (StateT Bool IO) ()
我打算这样使用堆栈
ret :: Stack
ret = apply
apply :: (MonadTuple m, MonadBool m) => m ()
apply = undefined
编译器不高兴,因为它在尝试检查 Stack
是否符合 MonadBool
时无法将 Bool
与 (Int, Int)
匹配。
我知道 Combining multiple states in StateT 中给出的解决方案。除了 arrows 或 global state with lens 之外,还有其他更简单的解决方案吗?
附录:
完整的代码块是
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad.State.Class
import Control.Monad.State.Lazy
type MonadTuple m = MonadState (Int, Int) m
type MonadBool m = MonadState Bool m
type Stack = StateT (Int, Int) (StateT Bool IO) ()
ret :: Stack
ret = apply
apply :: (MonadTuple m, MonadBool m) => m ()
apply = undefined
The definition of MonadState
具有函数依赖性 m -> s
,这意味着一个 monad m
最多只能有一个 MonadState s m
实例。或者,用更简单的术语来说,同一个 monad 不能有两个不同状态的 MonadState
实例,这正是你想要做的。
有一个更简单的解决方案:
apply :: (MonadTuple (t m), MonadBool m, MonadTrans t) => t m ()
apply = undefined
你可以在apply
里面用get
和put
去触摸(Int, Int)
状态,用lift get
和lift . put
去触摸Bool
状态。
然而,这需要 StateT (Int, Int)
成为 top-level 变压器。如果它低于顶部,则需要通过在类型中放置适当数量的附加转换器来对深度进行编码;例如如果这是第三件事,那么你需要
apply :: (MonadTuple (t1 (t2 (t3 m))), MonadBool m, MonadTrans t1, MonadTrans t2, MonadTrans t3) => t1 (t2 (t3 m)) ()
apply = undefined
并且每次访问 Bool
状态都需要使用三个 lift
s,这很快就会变得笨拙并且真的失去了 mtl-style class-polymorphic 编程的魅力.
一种常见的替代样式是公开一个 API 触及两个状态但不是 class 多态的。例如,
type Stack = StateT (Int, Int) (StateT Bool IO)
getTuple :: Stack (Int, Int)
getTuple = get
getBool :: Stack Bool
getBool = lift get
(同样你会添加一个 putTuple
和 putBool
。)
我想对于现代扩展,您也可以考虑引入您自己的 class,它没有 MonadState
所具有的资金;例如
class MonadState2 s m where
get2 :: m s
put2 :: s -> m ()
然后您可以使用新类型给出两个实例,以按类型消除歧义:
newtype Stack a = Stack (StateT (Int, Int) (StateT Bool IO) a)
instance MonadState2 Bool Stack where
get2 = Stack (lift get)
put2 = Stack . lift . put
instance MonadState2 (Int, Int) Stack where
get2 = Stack get
put2 = Stack . put
然后来电者会写,例如get2 @Bool
或 get2 @(Int, Int)
如果类型推断没有足够的信息来选择要使用的实例。但我怀疑这会很快变老。
我在一个 Haskell 应用程序中要跟踪两个或多个独立状态。
我正在使用
声明两个新类型 类type MonadTuple m = MonadState (Int, Int) m
type MonadBool m = MonadState Bool m
monad 转换器堆栈声明为
type Stack = StateT (Int, Int) (StateT Bool IO) ()
我打算这样使用堆栈
ret :: Stack
ret = apply
apply :: (MonadTuple m, MonadBool m) => m ()
apply = undefined
编译器不高兴,因为它在尝试检查 Stack
是否符合 MonadBool
时无法将 Bool
与 (Int, Int)
匹配。
我知道 Combining multiple states in StateT 中给出的解决方案。除了 arrows 或 global state with lens 之外,还有其他更简单的解决方案吗?
附录: 完整的代码块是
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad.State.Class
import Control.Monad.State.Lazy
type MonadTuple m = MonadState (Int, Int) m
type MonadBool m = MonadState Bool m
type Stack = StateT (Int, Int) (StateT Bool IO) ()
ret :: Stack
ret = apply
apply :: (MonadTuple m, MonadBool m) => m ()
apply = undefined
The definition of MonadState
具有函数依赖性 m -> s
,这意味着一个 monad m
最多只能有一个 MonadState s m
实例。或者,用更简单的术语来说,同一个 monad 不能有两个不同状态的 MonadState
实例,这正是你想要做的。
有一个更简单的解决方案:
apply :: (MonadTuple (t m), MonadBool m, MonadTrans t) => t m ()
apply = undefined
你可以在apply
里面用get
和put
去触摸(Int, Int)
状态,用lift get
和lift . put
去触摸Bool
状态。
然而,这需要 StateT (Int, Int)
成为 top-level 变压器。如果它低于顶部,则需要通过在类型中放置适当数量的附加转换器来对深度进行编码;例如如果这是第三件事,那么你需要
apply :: (MonadTuple (t1 (t2 (t3 m))), MonadBool m, MonadTrans t1, MonadTrans t2, MonadTrans t3) => t1 (t2 (t3 m)) ()
apply = undefined
并且每次访问 Bool
状态都需要使用三个 lift
s,这很快就会变得笨拙并且真的失去了 mtl-style class-polymorphic 编程的魅力.
一种常见的替代样式是公开一个 API 触及两个状态但不是 class 多态的。例如,
type Stack = StateT (Int, Int) (StateT Bool IO)
getTuple :: Stack (Int, Int)
getTuple = get
getBool :: Stack Bool
getBool = lift get
(同样你会添加一个 putTuple
和 putBool
。)
我想对于现代扩展,您也可以考虑引入您自己的 class,它没有 MonadState
所具有的资金;例如
class MonadState2 s m where
get2 :: m s
put2 :: s -> m ()
然后您可以使用新类型给出两个实例,以按类型消除歧义:
newtype Stack a = Stack (StateT (Int, Int) (StateT Bool IO) a)
instance MonadState2 Bool Stack where
get2 = Stack (lift get)
put2 = Stack . lift . put
instance MonadState2 (Int, Int) Stack where
get2 = Stack get
put2 = Stack . put
然后来电者会写,例如get2 @Bool
或 get2 @(Int, Int)
如果类型推断没有足够的信息来选择要使用的实例。但我怀疑这会很快变老。