Penso che fare i calcoli che richiedono numeri casuali all'interno di una monade che astrae il generatore è la cosa più pulita. Ecco come dovrebbe apparire il codice:
Metteremo l'istanza StdGen in una monade di stato, quindi forniremo dello zucchero sul metodo get e set del monad di stato per fornirci numeri casuali.
In primo luogo, caricare i moduli:
import Control.Monad.State (State, evalState, get, put)
import System.Random (StdGen, mkStdGen, random)
import Control.Applicative ((<$>))
(Normalmente avrei probabilmente non specificare le importazioni, ma questo lo rende facile da capire dove ogni funzione è venuta da.)
Poi ci definirà i nostri calcoli "che richiedono numeri casuali"; in questo caso, un alias per State StdGen
chiamato R
. (Perché "Random" e "Rand" significano già qualcos'altro.)
type R a = State StdGen a
Il modo in cui R funziona è: si definisce un calcolo che richiede un flusso di numeri casuali (l ' "azione" monadica), e poi si "esegue" con runRandom
:
runRandom :: R a -> Int -> a
runRandom action seed = evalState action $ mkStdGen seed
Ciò richiede un'azione e un seme e restituisce i risultati dell'azione. Proprio come i soliti evalState
, runReader
, ecc.
Ora abbiamo solo bisogno di zucchero attorno alla monade di Stato. Usiamo get
per ottenere StdGen e usiamo put
per installare un nuovo stato. Ciò significa, per ottenere un numero casuale, scriveremo:
rand :: R Double
rand = do
gen <- get
let (r, gen') = random gen
put gen'
return r
otteniamo lo stato corrente del generatore di numeri casuali, utilizzarlo per ottenere un nuovo numero casuale e un nuovo generatore, salvare il numero casuale, installare il nuovo stato del generatore e restituisce il numero casuale.
Si tratta di un "" azione" che può essere eseguito con runRandom, quindi proviamo:
ghci> runRandom rand 42
0.11040701265689151
it :: Double
Questa è una funzione di pura, quindi se lo si esegue di nuovo con gli stessi argomenti, si otterrà il stesso risultato. L'impurità rimane all'interno dell'azione che si passa a runRandom.
In ogni caso, la funzione vuole coppie di numeri casuali, quindi cerchiamo di scrivere un'azione per produrre un due di numeri casuali:
randPair :: R (Double, Double)
randPair = do
x <- rand
y <- rand
return (x,y)
Esegui questo con runRandom, e vedrete due numeri diversi in la coppia, come ti aspetteresti. Ma notate che non avete dovuto fornire "rand" con un argomento; questo perché le funzioni sono pure, ma "rand" è un'azione, che non deve essere pura.
Ora possiamo implementare la tua funzione normals
. boxMuller
è come definito sopra, ho solo aggiunto una firma di tipo così posso capire quello che sta succedendo, senza dover leggere l'intera funzione:
boxMuller :: Double -> Double -> (Double, Double) -> Double
boxMuller mu sigma (r1,r2) = mu + sigma * sqrt (-2 * log r1) * cos (2 * pi * r2)
Con tutte le funzioni di supporto/azioni attuate, possiamo finalmente scrivere normals
, un'azione di 0 argomenti che restituisce un (pigramente-generata) lista infinita di normalmente distribuito Doppio:
normals :: R [Double]
normals = mapM (\_ -> boxMuller 0 1 <$> randPair) $ repeat()
si potrebbe anche scrivere questo meno conciso se si desidera:
oneNormal :: R Double
oneNormal = do
pair <- randPair
return $ boxMuller 0 1 pair
normals :: R [Double]
normals = mapM (\_ -> oneNormal) $ repeat()
repeat()
conferisce all'azione monadica un flusso infinito di nulla per invocare la funzione con (ed è ciò che rende infinitamente lungo il risultato delle normali). Avevo inizialmente scritto [1..]
, ma l'ho riscritto per rimuovere l'inutile 1
dal testo del programma. Non stiamo operando su interi, vogliamo solo una lista infinita.
In ogni caso, è tutto.Per utilizzare questo in un vero e proprio programma, basta fare il vostro lavoro richiede normali casuali all'interno di un'azione R:
someNormals :: Int -> R [Double]
someNormals x = liftM (take x) normals
myAlgorithm :: R [Bool]
myAlgorithm = do
xs <- someNormals 10
ys <- someNormals 10
let xys = zip xs ys
return $ uncurry (<) <$> xys
runRandom myAlgorithm 42
Le tecniche usuali per la programmazione delle azioni monadici applicano; mantieni il più possibile la tua applicazione nel R
e le cose non saranno troppo confuse.
Oh, e su altra cosa: la pigrizia può "trapelare" al di fuori del confine della monade in modo pulito. Ciò significa che è possibile scrivere:
take 10 $ runRandom normals 42
e funzionerà.
Perché questo è un wiki? Sembra una domanda semplice e comprensibile. – Dan
Oops. Ho controllato la casella wiki per errore. –