混合并匹配State monad中的有状态计算 [英] Mix and match stateful computations within the State monad

查看:111
本文介绍了混合并匹配State monad中的有状态计算的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的程序状态由三个值组成: a b 类型 A B C / code>。不同的功能需要访问不同的值。我想使用 State monad编写函数,以便每个函数只能访问它需要访问的状态。



我有以下四种类型的函数:

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

以下是我如何调用 g f

  f = do 
- 一些东西
- y被绑定到某处某处的表达式
- 更多东西
x< - g'y
- 更多东西

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



< p <> code> g'
函数是一个丑陋的样板,除了缩小类型(A,B,C)之间的差距外, code>和(A,B)。它基本上是一个 g 的版本,它以三元组状态运行,但保持第三个元组项不变。我正在寻找一种方法来编写 f 没有这样的样板。可能是这样的:

pre $
$ stuff
x< - convert(0, 1,2)(gy)
- more stuff

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




  • convert(2,0,1 )将状态(c,a)x 转换为状态(a,b,c)x

  • convert(0,1)转换状态bx code> State(a,b)x

  • convert(0,2,1,0)转换状态(c,b)x 状态(a,b,c,d)x



我的问题:
$ b


  1. 是否有比将状态值放入元组更好的解决方案?我想过使用monad变压器堆栈。然而,我认为只有在任何两个函数 f g F G G F F f 所需的状态值集合, G g 所需的一组状态值。我错了吗? (请注意,我的示例不满足此属性,例如, G = {a,b} H = {b,c} 两者都不是其他的子集。)

  2. 如果没有比元组更好的方法,那么是否有避免我提到的样板的好方法?我甚至愿意编写一个带有一系列样板函数的文件(见下文),只要该文件可以自动生成一次然后被遗忘。有没有更好的办法? (我已经读过关于镜头的内容,但是他们的复杂性,丑陋的语法,巨大的不必要的功能以及对Haskell模板的依赖都是令人厌恶的。这是我的一个误解吗?镜头能够以避免这些问题的方式解决我的问题吗? ?)

(我提到的函数看起来像这样)。

  convert_0_1_2 :: State(a,b)x  - >状态(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 - >状态(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


解决方案

您可以使用 lens-family lens 包与 tuple-lenses 包: zoom 的简化类型是:

  zoom :: Lens'sa  - >陈述一个x  - > State sx 

因此 zoom 一个较小的状态。 Lens 用于指定较大状态 s 中较小状态 a 的位置。 / code>。



使用这两个包,您可以运行 g h i 如下:

  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)


The state of my program consists of three values, a, b, and c, of types A, B, and C. Different functions need access to different values. I want to write functions using the State monad so that each function can only access the pieces of the state that it needs to access.

I have four functions of the following types:

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

Here is how I call g within 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

That g' function is an ugly piece of boilerplate that does nothing but bridge the gap between the types (A, B, C) and (A, B). It is basically a version of g that operates on a 3-tuple state, but leaves the 3rd tuple item unchanged. I am looking for a way to write f without that boilerplate. Maybe something like this:

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

Where convert (0,1,2) converts a computation of type State (a, b) x to type State (a, b, c) x. Likewise, for all types a, b, c, d:

  • convert (2,0,1) converts State (c,a) x to State (a,b,c) x
  • convert (0,1) converts State b x to State (a,b) x
  • convert (0,2,1,0) converts State (c,b) x to State (a,b,c,d) x

My questions:

  1. Is there a better solution than putting state values in tuples? I thought about using a monad transformer stack. However, I think that only works if, for any two functions f and g, either FG or GF, where F is the set of state values needed by f and G is the set of state values needed by g. Am I wrong about that? (Note that my example does not satisfy this property. For example, G = {a, b} and H = {b, c}. Neither is a subset of the other.)
  2. If there is no better way than tuples, then is there a good way to avoid the boilerplate I mentioned? I am even willing to write a file with a bunch of boilerplate functions (see below) as long as the file can be automatically generated once and then forgotten about. Is there a better way? (I have read about lenses, but their complexity, ugly syntax, enormous set of unnecessary features, and seeming reliance on Template Haskell are off-putting. Is that a misconception of mine? Can lenses solve my problem in a way that avoids those problems?)

(The functions I mentioned would look something like this.)

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

解决方案

You can do it by using zoom from lens-family or the lens package with the tuple-lenses package: the simplified type of zoom is:

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

So zoom runs a computation using a smaller state. The Lens is used to specify the location of smaller state a inside the larger state s.

With these two packages, you can run g, h and i as follows:

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)

这篇关于混合并匹配State monad中的有状态计算的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆