【问题标题】:Haskell -- Chaining two states using StateT monad transformersHaskell - 使用 StateT monad 转换器链接两个状态
【发布时间】:2018-04-11 18:31:23
【问题描述】:

我在一个 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 中给出的解决方案。除了箭头带镜头的全局状态之外,还有其他更简单的解决方案吗?

附录: 完整的代码块是

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

【问题讨论】:

  • 您将其命名为Stack 有什么原因吗?
  • 我将其命名为 Stack 以建议使用 monad 转换器 stack
  • 在这种情况下,它不应该将硬编码的() 作为值类型。
  • 我同意。如果以多态方式使用转换器,则不硬编码会更清晰。

标签: haskell monad-transformers state-monad


【解决方案1】:

The definition of MonadState 有一个函数依赖m -> s,这意味着一个monad m 最多只能有一个MonadState s m 的实例。或者,更简单地说,同一个 monad 不能有两个不同状态的 MonadState 实例,这正是您想要做的。

【讨论】:

    【解决方案2】:

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

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

    您可以在apply 中使用getput 来触摸(Int, Int) 状态,使用lift getlift . put 来触摸Bool 状态。

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

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

    并且每次访问Bool 状态都需要使用三个lifts,这很快就会变得笨拙,并且真的失去了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;例如

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多