Я задавал один и тот же вопрос один раз. Теперь я буду более конкретным. Цель состоит в том, чтобы изучить икону Haskell для написания итеративных алгоритмов с моноданными результатами. В частности, это может быть полезно для реализации всех видов рандомизированных алгоритмов, таких как генетические алгоритмы и т.п.
Я написал примерную программу, которая проявляет мою проблему с такими алгоритмами в Haskell. Его полный источник находится на hpaste.
Ключевым моментом является случайное обновление элемента (таким образом, результат находится в State StdGen
или какой-либо другой монаде):
type RMonad = State StdGen
-- An example of random iteration step: one-dimensional random walk.
randStep :: (Num a) => a -> RMonad a
randStep x = do
rnd <- get
let (goRight,rnd') = random rnd :: (Bool, StdGen)
put rnd'
if goRight
then return (x+1)
else return (x-1)
И тогда нужно обновить многие элементы и повторить процесс много, много раз. И вот проблема. Поскольку каждый шаг - действие монады (:: a -> m a
), повторяющееся много раз, важно эффективно выполнять такие действия (быстро забывая о предыдущем шаге). Из того, что я узнал из своего предыдущего вопроса (Составление действий монады со сгибами), seq
и deepseq
помогают многому составить монадические действия. Поэтому я:
-- Strict (?) iteration.
iterateM' :: (NFData a, Monad m) => Int -> (a -> m a) -> a -> m a
iterateM' 0 _ x = return $!! x
iterateM' n f x = (f $!! x) >>= iterateM' (n-1) f
-- Deeply stict function application.
($!!) :: (NFData a) => (a -> b) -> a -> b
f $!! x = x `deepseq` f x
Это, безусловно, лучше ленивой композиции. К сожалению, этого недостаточно.
-- main seems to run in O(size*iters^2) time...
main :: IO ()
main = do
(size:iters:_) <- liftM (map read) getArgs
let start = take size $ repeat 0
rnd <- getStdGen
let end = flip evalState rnd $ iterateM' iters (mapM randStep) start
putStr . unlines $ histogram "%.2g" end 13
Когда я измерял время, необходимое для завершения этой программы, кажется, что он похож на O (N ^ 2) относительно количества итераций (распределение памяти представляется приемлемым). Этот профиль должен быть плоским и постоянным для линейной асимптотики:
квадратное время на обновление http://i29.tinypic.com/i59blv.png
И вот так выглядит профиль кучи:
профиль кучи с -hc http://i30.tinypic.com/124a8fc.png
Я предполагаю, что такая программа должна работать с очень скромными требованиями к памяти, и она должна занимать время пропорционально количеству итераций. Как я могу достичь этого в Haskell?
Полный исходный источник примера здесь.