考虑以下简化的例子:
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
,对于2D操作既有
head a
也有
head b
)。然而,在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]
b <- walk11_head
return $ zipWith (\x y -> x + y + z) a b
walk12_head = do
z <- st1
a <- walk11_head
b <- return [0]
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]
b <- walk11_head
_ <- walk11_tail
return $ zipWith (\x y -> x + y + z) a b
walk12_head = do
z <- st1
a <- walk11_head
b <- return [0]
_ <- walk11_tail
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
是否真正依赖于head $ walk 1 2
引发的walk 1 1
操作序列的状态依赖性。