Je pense que faire vos calculs qui nécessitent des nombres aléatoires à l'intérieur d'une monade qui fait abstraction du générateur est la chose la plus propre. Voici à quoi ressemblera ce code:
Nous allons mettre l'instance StdGen dans un monad state, puis fournir un peu de sucre sur la méthode get et set de la monad de l'état pour nous donner des nombres aléatoires.
En premier lieu, charger les modules:
import Control.Monad.State (State, evalState, get, put)
import System.Random (StdGen, mkStdGen, random)
import Control.Applicative ((<$>))
(Normalement, je ne serais probablement pas préciser les importations, mais cela le rend facile à comprendre où chaque fonction vient.)
Ensuite, nous allons définir nos "calculs nécessitant des nombres aléatoires" monad; dans ce cas, un alias pour State StdGen
appelé R
. (Parce que "Random" et "Rand" signifient déjà quelque chose d'autre.)
type R a = State StdGen a
La façon dont R fonctionne est: vous définissez un calcul qui nécessite un flux de nombres aléatoires (la monade « action »), puis vous « exécutez » avec runRandom
:
runRandom :: R a -> Int -> a
runRandom action seed = evalState action $ mkStdGen seed
Cela prend une action et une graine, et renvoie les résultats de l'action. Tout comme les habituels evalState
, runReader
, etc.
Maintenant, nous avons juste besoin de sucre autour de la monade de l'Etat. Nous utilisons get
pour obtenir le StdGen, et nous utilisons put
pour installer un nouvel état. Cela signifie, pour obtenir un nombre aléatoire, nous écrivions:
rand :: R Double
rand = do
gen <- get
let (r, gen') = random gen
put gen'
return r
On a l'état actuel du générateur de nombres aléatoires, l'utiliser pour obtenir un nouveau numéro aléatoire et un nouveau générateur, enregistrer le numéro aléatoire, installez le nouvel état du générateur et renvoie le nombre aléatoire.
Ceci est une « action » qui peut être exécuté avec runRandom, donc nous allons essayer:
ghci> runRandom rand 42
0.11040701265689151
it :: Double
Ceci est une pure fonction, donc si vous l'exécutez à nouveau avec les mêmes arguments, vous obtiendrez le même résultat. L'impureté reste à l'intérieur de "l'action" que vous passez à runRandom.
Quoi qu'il en soit, votre fonction veut paires de nombres aléatoires, donc nous allons écrire une action pour produire une paire de nombres aléatoires:
randPair :: R (Double, Double)
randPair = do
x <- rand
y <- rand
return (x,y)
Exécuter ce avec runRandom, et vous verrez deux numéros différents la paire, comme vous l'attendez. Mais remarquez que vous n'aviez pas à fournir "rand" avec un argument; C'est parce que les fonctions sont pures, mais "rand" est une action qui n'a pas besoin d'être pure.
Maintenant, nous pouvons implémenter votre normals
fonction. boxMuller
est comme vous l'avez défini ci-dessus, je viens d'ajouter une signature de type pour que je puisse comprendre ce qui se passe sans avoir à lire toute la fonction:
boxMuller :: Double -> Double -> (Double, Double) -> Double
boxMuller mu sigma (r1,r2) = mu + sigma * sqrt (-2 * log r1) * cos (2 * pi * r2)
Avec toutes les fonctions d'aide/actions mises en œuvre, nous pouvons enfin écrire normals
, une action de 0 arguments qui renvoie un (paresseusement généré) liste infinie de Doubles normalement distribués:
normals :: R [Double]
normals = mapM (\_ -> boxMuller 0 1 <$> randPair) $ repeat()
vous pouvez également écrire ce moins de façon concise si vous voulez:
oneNormal :: R Double
oneNormal = do
pair <- randPair
return $ boxMuller 0 1 pair
normals :: R [Double]
normals = mapM (\_ -> oneNormal) $ repeat()
repeat()
donne l'action monadique un flux infini de rien pour invoquer la fonction avec (et est ce qui rend le résultat des normales infiniment long). J'avais initialement écrit [1..]
, mais je l'ai réécrit pour enlever le 1
sans signification du texte du programme. Nous ne fonctionnons pas sur des entiers, nous voulons juste une liste infinie.
Quoi qu'il en soit, c'est tout.Pour l'utiliser dans un programme réel, vous venez de faire votre travail nécessitant Normales au hasard dans une action 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
Les techniques habituelles pour les actions monades de programmation applicables; Gardez aussi peu de votre application dans R
que possible, et les choses ne seront pas trop en désordre.
Oh, et d'autre part: la paresse peut "fuir" proprement à l'extérieur de la frontière de la monade. Cela signifie que vous pouvez écrire:
take 10 $ runRandom normals 42
et cela fonctionnera.
Pourquoi est-ce un wiki? Cela ressemble à une question directe. – Dan
Oups. J'ai vérifié la boîte wiki par erreur. –