考虑以下简化示例:
import Control.Monad.State.Lazy
st :: [State Int Int]
st = [state (\s -> (s, s + 1)), undefined]
action1d = do
a <- sequence st
return $ map (2*) a
action2d = do
a <- sequence st
b <- sequence st
return $ zipWith (+) a b
main :: IO ()
main = do
print $ head $ evalState action1d 0
print $ head $ evalState action2d 0
在这里,在 1D 和 2D 计算中,结果的头显式仅取决于输入的头(对于 1D 动作,head a 和 head a 和 head b 对于 2D 动作) .但是,在 2D 计算中,b(甚至只是它的头部)对当前状态存在隐式依赖,并且该状态取决于对整体的评估a,不仅仅是它的头。
您的示例中有类似的依赖关系,尽管它被状态操作列表的使用所掩盖。
假设我们想要手动运行操作walk22_head = head $ walk 2 2 并检查结果列表中的第一个整数:
main = print $ head $ evalState walk22_head
明确写出状态动作列表st的元素:
st1, st2 :: State Int Int
st1 = state (\s -> (s, s+1))
st2 = undefined
我们可以把walk22_head写成:
walk22_head = do
z <- st1
a <- walk21_head
b <- walk12_head
return $ zipWith (\x y -> x + y + z) a b
请注意,这仅取决于定义的状态操作st1 以及walk 2 1 和walk 1 2 的头部。反过来,这些头可以写成:
walk21_head = do
z <- st1
a <- return [0] -- walk20_head
b <- walk11_head
return $ zipWith (\x y -> x + y + z) a b
walk12_head = do
z <- st1
a <- walk11_head
b <- return [0] -- walk02_head
return $ zipWith (\x y -> x + y + z) a b
同样,这些仅取决于定义的状态操作st1 和walk 1 1 的头部。
现在,让我们试着写下walk11_head的定义:
walk11_head = do
z <- st1
a <- return [0]
b <- return [0]
return $ zipWith (\x y -> x + y + z) a b
这仅取决于定义的状态操作st1,因此有了这些定义,如果我们运行main,我们会得到一个明确的答案:
> main
10
但这些定义并不准确!在walk 1 2 和walk 2 1 中的每一个中,头部动作都是动作的序列,从调用walk11_head 的动作开始,然后继续基于walk11_tail 的动作。因此,更准确的定义是:
walk21_head = do
z <- st1
a <- return [0] -- walk20_head
b <- walk11_head
_ <- walk11_tail -- side effect of the sequennce
return $ zipWith (\x y -> x + y + z) a b
walk12_head = do
z <- st1
a <- walk11_head
b <- return [0] -- walk02_head
_ <- walk11_tail -- side effect of the sequence
return $ zipWith (\x y -> x + y + z) a b
与:
walk11_tail = do
z <- undefined
a <- return [0]
b <- return [0]
return [zipWith (\x y -> x + y + z) a b]
有了这些定义,单独运行walk12_head 和walk21_head 就没有问题了:
> head $ evalState walk12_head 0
1
> head $ evalState walk21_head 0
1
这里的状态副作用不需要计算答案,因此从不调用。但是,不可能同时运行它们:
> head $ evalState (walk12_head >> walk21_head) 0
*** Exception: Prelude.undefined
CallStack (from HasCallStack):
error, called at libraries/base/GHC/Err.hs:78:14 in base:GHC.Err
undefined, called at Lazy2D_2.hs:41:8 in main:Main
因此,尝试运行 main 失败的原因相同:
> main
*** Exception: Prelude.undefined
CallStack (from HasCallStack):
error, called at libraries/base/GHC/Err.hs:78:14 in base:GHC.Err
undefined, called at Lazy2D_2.hs:41:8 in main:Main
因为在计算walk22_head时,即使是最开始walk21_head的计算也依赖于walk11_tail发起的状态副作用walk12_head。
您原来的 walk 定义与这些模型的行为方式相同:
> head $ evalState (head $ walk 1 2) 0
1
> head $ evalState (head $ walk 2 1) 0
1
> head $ evalState (head (walk 1 2) >> head (walk 2 1)) 0
*** Exception: Prelude.undefined
CallStack (from HasCallStack):
error, called at libraries/base/GHC/Err.hs:78:14 in base:GHC.Err
undefined, called at Lazy2D_0.hs:15:49 in main:Main
> head $ evalState (head (walk 2 2)) 0
*** Exception: Prelude.undefined
CallStack (from HasCallStack):
error, called at libraries/base/GHC/Err.hs:78:14 in base:GHC.Err
undefined, called at Lazy2D_0.hs:15:49 in main:Main
很难说如何解决这个问题。您的玩具示例非常适合说明问题,但尚不清楚在您的“真实”问题中如何使用状态,以及 head $ walk 2 1 是否真的对 walk 1 1 的 walk 1 1 动作有状态依赖性head $ walk 1 2.