我正在努力解决如何在 Haskell 中进行有状态计算懒惰地生成结果的一般问题。例如,下面的简单算法可以在 Python 的生成器工具的帮助下表示为有状态但“惰性”的计算,只执行到达下一条yield
语句所需的步骤,然后将控制流返回给调用者,直到请求下一个元素:
def solveLP(vmax0, elems):
elem_true_ixs = [ [ ei for ei, b in enumerate(row) if b ] for row in elems ]
return go(vmax0, elem_true_ixs)
def go(vmax, mms):
if not mms:
yield []
else:
for ei in mms[0]:
maxcnt = vmax[ei]
if not maxcnt > 0:
continue
vmax[ei] = maxcnt-1 # modify vmax vector in-place
for es in go(vmax, mms[1:]):
# note: inefficient vector-concat operation
# but not relevant for this question
yield [ei]+es
vmax[ei] = maxcnt # restore original vmax state
for sol in solveLP([1,2,3],[[True,True,False],[True,False,True]]):
print sol
# prints [0,2], [1,0], and [1,2]
这可以很容易地转换为惰性 Haskell 计算(例如,当m
专门用于Logic
or时[]
),例如
import Control.Monad
import qualified Data.Vector.Unboxed as VU
solveLP :: MonadPlus m => VU.Vector Int -> [[Bool]] -> m [Int]
solveLP vmax0 elems = go vmax0 elemTrueIxs
where
-- could be fed to 'sequence'
elemTrueIxs = [ [ ei | (ei,True) <- zip [0::Int ..] row ] | row <- elems ]
go vmax [] = return []
go vmax (m:ms) = do
ei <- mlist m
let vmax' = vmax VU.// [(ei, maxcnt-1)] -- this operation is expensive
maxcnt = vmax VU.! ei
guard $ maxcnt > 0
es <- go vmax' ms
return $ (ei:es)
mlist = msum . map return
...但我希望能够通过使用可变向量并vmax0
就地修改单个向量(因为我只需要增加/减少单个元素并复制整个仅替换单个元素的向量是一个相当大的开销,向量变得越长);请注意,这只是我一直在尝试实现的一类算法的玩具示例
所以我的问题是——假设有一种方法可以实现这一点——我如何在 ST monad 中表达这样一个有状态的算法,同时在计算过程中一旦产生结果,仍然能够将结果返回给调用者?我尝试通过 monad-transformers 将 ST monad 与 list monad 结合起来,但我不知道如何使它工作......