我的程序状态由三个值组成,分别是类型为A
、B
和C
的a
、b
和c
。不同的函数需要访问不同的值。我想使用State
单子编写函数,以便每个函数只能访问它需要访问的状态片段。
我有以下四个函数:
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
的一个版本,但保持第三个元素不变。我正在寻找一种不需要这种样板代码的编写 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
的计算。同样,对于所有类型a
,b
,c
,d
:
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
的计算
我的问题:
- 除了将状态值放在元组中,有没有更好的解决方案?我考虑使用单子变换器堆栈。但是,我认为只有当任意两个函数
f
和g
满足F
⊆G
或G
⊆F
时,才能够实现。其中F
是f
所需的状态值的集合,G
是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
lens
的zoom
:http://hackage.haskell.org/package/lens-4.13/docs/Control-Lens-Zoom.html#v:zoom - Joachim Breitnerlens
+vinyl
可以带来一种半不错的 Haskell 自虐体验。 - András Kovács