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

Jor*_*dan 14 monads haskell functional-programming

我的程序的状态包含三个值,a,b,和c,类型A,BC.不同的功能需要访问不同的值.我想使用Statemonad 编写函数,以便每个函数只能访问它需要访问的状态段.

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

f :: State (A, B, C) x
g :: y -> State (A, B) x
h :: y -> State (B, C) x
i :: y -> State (A, C) x
Run Code Online (Sandbox Code Playgroud)

以下是我打电话的g内容f:

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
Run Code Online (Sandbox Code Playgroud)

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

f = do
    -- stuff
    x <- convert (0,1,2) (g y)
    -- more stuff
Run Code Online (Sandbox Code Playgroud)

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

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

我的问题:

  1. 有没有比将状态值放在元组中更好的解决方案?我想过使用monad变压器堆栈.不过,我认为只有工作,如果对于任何两个函数fg,无论是FGGF,这里F是一组由所需的状态值的fG是一组由所需的状态值g.我错了吗?(请注意,我的示例不满足此属性.例如,G= {a, b}H= {b, c}.两者都不是另一个的子集.)
  2. 如果没有比元组更好的方法,那么有没有一种方法可以避免我提到的样板?我甚至愿意用一堆样板函数编写一个文件(见下文),只要该文件可以自动生成一次然后被遗忘.有没有更好的办法?(我读过关于镜头的内容,但是它们的复杂性,难看的语法,大量不必要的功能,以及对模板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
Run Code Online (Sandbox Code Playgroud)

ben*_*ofs 9

您可以使用zoom from lens-familylens包中的tuple-lenses包来实现:简化类型zoom是:

zoom :: Lens' s a -> State a x -> State s x
Run Code Online (Sandbox Code Playgroud)

因此,zoom使用较小的状态运行计算.将Lens用于指定较小的状态的位置a的较大的状态内s.

有了这两个包,可以运行g,hi如下:

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)
Run Code Online (Sandbox Code Playgroud)