Ho usato per chiedere una domanda simile once. Ora sarò più specifico. Lo scopo è quello di apprendere un idioma Haskell per scrivere algoritmi iterativi con risultati monadici. In particolare, questo potrebbe essere utile per implementare tutti i tipi di algoritmi randomizzati, come algoritmi genetici e simili.Iterazione di un algoritmo randomizzato in spazio fisso e tempo lineare
Ho scritto un programma di esempio che manifesta il mio problema con tali algoritmi in Haskell. La sua fonte completa è hpaste.
Il punto chiave è aggiornare un elemento casuale (quindi il risultato è in State StdGen
o qualche altra monade):
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)
E poi uno deve aggiornare molti elementi, e ripetere il processo, molte volte . E qui c'è un problema. Poiché ogni passo è un'azione di monade (:: a -> m a
), ripetuta molte volte, è importante comporre tali azioni in modo efficace (dimenticando rapidamente il passaggio precedente). Da quanto ho appreso dalla mia precedente domanda, (Composing monad actions with folds), seq
e deepseq
, ho aiutato molto a comporre azioni monadiche. Così faccio:
-- 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
È sicuramente meglio della composizione pigra. Sfortunatamente, non è abbastanza.
-- 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
Quando ho misurato il tempo richiesto per completare questo programma, sembra, che è simile a O (N^2) rispetto al numero di iterazioni (allocazione di memoria sembra essere accettabile). Questo profilo dovrebbe essere piatta e costante per asintotica lineari:
quadratic time per update http://i29.tinypic.com/i59blv.png
E questo è come un profilo mucchio sembra:
heap profile with -hc http://i30.tinypic.com/124a8fc.png
presumo che un tale programma deve essere eseguito con la memoria molto modesto requisiti, e dovrebbe richiedere tempo proporzionale al numero di iterazioni. Come posso ottenerlo in Haskell?
La sorgente eseguibile completa dell'esempio è here.
domanda ingenua: non dovrebbe essere un generatore casuale di un comonad invece, dal momento che è una sorta di flusso?Non tutti i generatori casuali sono stati, e preferisco "estrarre" un numero casuale, non "tornare al suo stato". Il tuo codice sembra troppo "imperativo" per me. –
@Alex: Ma System.Random è integrato, mentre Control.Comonad.Random richiede l'installazione di molti pacchetti e la conoscenza di comonad per trovarlo: |. – kennytm
Non sono sicuro di cosa intendi per "over-linear". Potresti spiegare un po 'più a fondo cos'è il cattivo comportamento? – sclv