Haskell - 使用StateT monad变换器链接两个州

时间:2018-04-11 18:31:24

标签: haskell monad-transformers state-monad

我有两个或更多独立状态要在一个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

编译器不满意,因为在尝试检查Bool是否符合(Int, Int)时,StackMonadBool无法匹配。

我知道Combining multiple states in StateT中给出的解决方案。除了箭头带镜头的全局状态之外,还有其他更简单的解决方案吗?

附录: 完整的代码块是

{-# 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

2 个答案:

答案 0 :(得分:5)

The definition of MonadState具有功能依赖关系m -> s,这意味着一个monad m最多只能有一个MonadState s m个实例。或者,从更简洁的角度来看,同一个monad不能为两个不同的状态设置两个MonadState个实例,这正是你想要做的。

答案 1 :(得分:3)

有一个更简单的解决方案:

apply :: (MonadTuple (t m), MonadBool m, MonadTrans t) => t m ()
apply = undefined

您可以使用get内的putapply触及(Int, Int)州,lift getlift . put触及Bool 1}}状态。

但是,这要求StateT (Int, Int)是顶级转换器。如果它低于顶部,则需要通过在您的类型中添加适当数量的附加变换器来对深度进行编码;例如如果这是第三件事你需要

apply :: (MonadTuple (t1 (t2 (t3 m))), MonadBool m, MonadTrans t1, MonadTrans t2, MonadTrans t3) => t1 (t2 (t3 m)) ()
apply = undefined

并且需要使用三个lift来访问Bool状态,这很快变得笨拙并且真的失去了mtl风格的类多态编程的魅力。

一种常见的替代方式是公开一个触及两个状态但不是类多态的API。例如,

type Stack = StateT (Int, Int) (StateT Bool IO)

getTuple :: Stack (Int, Int)
getTuple = get

getBool :: Stack Bool
getBool = lift get

(同样,您需要添加putTupleputBool。)

我想现代扩展你也可以考虑引入你自己的类,它没有MonadState所具有的fundep; e.g。

class MonadState2 s m where
    get2 :: m s
    put2 :: s -> m ()

然后,您可以使用newtype来提供两个实例,并按类型消除歧义:

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 @Boolget2 @(Int, Int)如果类型推断没有足够的信息来选择要使用的实例。但我怀疑这会很快变老。