考虑以下简化示例:
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 计算中,结果的头部显式地仅取决于输入的头部(仅head a
适用于 1D 动作以及两者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
这仅取决于定义的状态 action 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
真的sequence
对walk 1 1
由head $ walk 1 2
.