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 計算に簡単に変換できます (たとえば、がorm
に特化されている場合)。Logic
[]
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
...しかし、変更可能なベクトルを使用し、単一のベクトルをインプレースで変更することにより、元の Python 実装に近づけたいと思いますvmax0
(単一の要素をインクリメント/デクリメントし、全体をコピーするだけでよいため)単一の要素を置き換えるだけの vector は、vector が長くなるほどかなりのオーバーヘッドになります); これは、私が実装しようとしているアルゴリズムのクラスのおもちゃの例にすぎないことに注意してください
だから私の質問は、それを達成する方法があると仮定して、計算中に結果が生成されるとすぐに結果を呼び出し元に返すことができる一方で、ST モナドでそのようなステートフルなアルゴリズムを表現するにはどうすればよいですか? モナドトランスフォーマーを介してSTモナドとリストモナドを組み合わせてみましたが、それを機能させる方法がわかりませんでした...