首页 文章

混合并匹配State monad中的有状态计算

提问于
浏览
14

我的程序状态由三个值 abc 组成,类型为 ABC . 不同的功能需要访问不同的值 . 我想使用 State monad编写函数,以便每个函数只能访问它需要访问的状态段 .

我有以下四种类型的功能:

f :: State (A, B, C) x
g :: y -> State (A, B) x
h :: y -> State (B, C) x
i :: y -> State (A, C) x

以下是我在 f 中调用 g 的方法:

f = do
    -- some stuff
    -- y is bound to an expression somewhere in here
    -- more stuff
    x <- g' y
    -- even more stuff

    where g' y = do
              (a, b, c) <- get
              let (x, (a', b')) = runState (g y) (a, b)
              put (a', b', c)
              return x

那个 g' 函数是一个丑陋的样板,它只能弥补 (A, B, C)(A, B) 类型之间的差距 . 它基本上是 g 的一个版本,它以3元组状态运行,但保留第3个元组项目 . 我正在寻找一种方法来编写没有该样板的 f . 也许是这样的:

f = do
    -- stuff
    x <- convert (0,1,2) (g y)
    -- more stuff

其中 convert (0,1,2) 将类型 State (a, b) x 的计算转换为类型 State (a, b, c) x . 同样,对于所有类型 abcd

  • convert (2,0,1)State (c,a) x 转换为 State (a,b,c) x

  • convert (0,1)State b x 转换为 State (a,b) x

  • convert (0,2,1,0)State (c,b) x 转换为 State (a,b,c,d) x

My questions:

  • 有没有比将状态值放在元组中更好的解决方案?我想过使用monad变换器堆栈 . 但是,我认为只有对于任何两个函数 fgFGGF ,其中 FfG 所需的状态值集合是 g 所需的状态值集合 . 我错了吗? (请注意,我的示例不满足此属性 . 例如, G = {a, b}H = {b, c} . 两者都不是另一个的子集 . )

  • 如果没有比元组更好的方法,那么有没有一种方法可以避免我提到的样板?我甚至愿意用一堆样板函数编写一个文件(见下文),只要该文件可以自动生成一次然后被遗忘 . 有没有更好的办法? (我读过有关镜头的内容,但它们的复杂性,难看的语法,大量不必要的功能以及对模板Haskell的依赖都令人反感 . 这是对我的误解吗?镜头可以解决我的问题以避免这些问题?)

(我提到的功能看起来像这样 . )

convert_0_1_2 :: State (a, b) x -> State (a, b, c) x
convert_0_1_2 f = do
    (a, b, c) <- get
    let (x, (a', b')) = runState f (a, b)
    put (a', b', c)
    return x

convert_0_2_1_0 :: State (c, b) x -> State (a, b, c, d) x
convert_0_2_1_0 f = do
    (a, b, c, d) <- get
    let (x, (b', c')) = runState f (b, c)
    put (a, b', c', d)
    return x

2 回答

  • 4

    您可以使用lens-family中的缩放或tuple-lenses包中的lens包来实现:简化类型 zoom 是:

    zoom :: Lens' s a -> State a x -> State s x
    

    所以 zoom 使用较小的状态运行计算 . Lens 用于指定较大状态 s 内较小状态 a 的位置 .

    使用这两个包,您可以运行 ghi ,如下所示:

    f :: State (A,B,C) x
    f = do
      zoom _12 g -- _12 :: Lens' (A,B,C) (A,B)
      zoom _23 h -- _23 :: Lens' (A,B,C) (B,C)
      zoom _13 i -- _13 :: Lens' (A,B,C) (A,C)
    
  • 9

    如果你想在 lens 包中使用一些奇特的模板Haskell来支持它,你也可以手工完成 . 这个想法是为州的每个部分创建至少一个类:

    class HasPoints s where
      points :: Lens' s Int
    
    class ReadsPoints s where
      getPoints :: Getter s Int
      default getPoints :: HasPoints s => Getter s Int
      getPoints = points
    
    class SetsPoints s where
      setPoints :: Setter' s Int
      ...
    

    然后,每个操纵状态的函数都会有类型签名

    fight :: (HasPoints s, ReadsHealth s) => StateT s Game Player
    

    具有此特定签名的操作具有对点的完全访问权限以及对运行状况的只读访问权限 .

相关问题