La funzione getRandomR
non funziona IO
. Non è necessario eseguire IO
per generare numeri casuali dopo aver ottenuto un seme. Il Rand
Monad in MonadRandom
viene inizializzato con un seme, che può essere uno fornito per scopi di test o uno estratto da IO utilizzando evalRandIO
. La Monade Rand
può eseguire questa operazione senza eseguire le azioni IO
sfruttando le funzioni pure esposte in System.Random
dal pacchetto random
, ad esempio random
e randomR
. Ognuna di queste funzioni richiede un generatore g
e restituisce un nuovo generatore e un valore casuale del tipo desiderato. Internamente, la Monade Rand
è in realtà solo la Monade State
e il suo stato è il generatore g
.
Tuttavia, è importante notare che il IO
Monade è un'istanza di MonadRandom
, dove invece di usare le funzioni di stato puro, utilizza le normali funzioni come IO
randomIO
. È possibile utilizzare IO
e Rand
in modo intercambiabile, ma quest'ultimo sarà un po 'più efficiente (non è necessario eseguire una chiamata di sistema ogni volta) e sarà possibile inizializzarlo con un valore noto a fini di test per ottenere risultati ripetibili.
Quindi, per rispondere alla tua domanda
Come si può dire se questa funzione computeSomething
sarà pura o impura?
Per questa definizione di computeSomething
, non è né pura o impura fino alla risoluzione del istanza per MonadRandom
. Se prendiamo "puro" per essere "non IO" e "impuro" per essere "IO" (which is not entirely accurate, but a close approximation), quindi computeSomething
può essere puro in alcuni casi e impuro in altri, proprio come la funzione liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
può essere utilizzata sul IO
Monade o sulle Monete Maybe
o []
. In altre parole:
liftM2 (+) (Just 1) (Just 2)
restituirà sempre Just 3
, in modo che possa essere considerato puro, mentre
liftM2 (++) getLine getLine
non tornerà sempre la stessa cosa.Mentre ogni istanza predefinita per MonadRandom
viene considerata impura (RandT
e hanno lo stato interno, quindi sono tecnicamente impure), è possibile fornire il proprio tipo di dati con un'istanza di MonadRandom
per la quale restituisce sempre lo stesso valore quando getRandom
o il valore vengono chiamate altre funzioni MonadRandom
. Per questo motivo, direi che MonadRandom
non è intrinsecamente puro o impuro.
Forse un po 'di codice contribuirà a spiegare (semplificato, sto saltando il RandT
trasformatore):
import Control.Monad.State
import qualified System.Random as R
class MonadRandom m where
getRandom :: Random a => m a
getRandoms :: Random a => m [a]
getRandomR :: Random a => (a, a) -> m a
getRandomRs :: Random a => (a, a) -> m [a]
-- Not the real definition, the MonadRandom library defines a RandT
-- Monad transformer where Rand g a = RandT g Identity a, with
-- newtype RandT g m a = RandT (StateT g m a), but I'm trying to
-- keep things simple for this example.
newtype Rand g a = Rand { unRand :: State g a }
instance Monad (Rand g) where
-- Implementation isn't relevant here
instance RandomGen g => MonadRandom (Rand g) where
getRandom = state R.random
getRandoms = sequence $ repeat getRandom
getRandomR range = state (R.randomR range)
getRandomRs range = sequence $ repeat $ getRandomR range
instance MonadRandom IO where
getRandom = R.randomIO
getRandoms = sequence $ repeat getRandom
getRandomR range = R.randomRIO range
getRandomRs range = sequence $ repeat $ getRandomR range
Così, quando abbiamo una funzione
computeSomething :: MonadRandom m => Int -> m Int
computeSomething high = getRandomR (0, high)
Poi possiamo usare it
main :: IO()
main = do
i <- computeSomething 10
putStrLn $ "A random number between 0 and 10: " ++ show i
O
main :: IO()
main = do
-- evalRandIO uses getStdGen and passes the generator in for you
i <- evalRandIO $ computeSomething 10
putStrLn $ "A random number between 0 and 10: " ++ show i
O se si voleva utilizzare un generatore di nota per testare con:
main :: IO()
main = do
let myGen = R.mkStdGen 12345
i = evalRand (computeSomething 10) myGen
putStrLn $ "A random number between 0 and 10: " ++ show i
In quest'ultimo caso, verrà stampato lo stesso numero ogni volta, facendo un processo di "random" deterministico e puro. Questo ti dà la possibilità di ri-eseguire esperimenti che generano numeri casuali fornendo un seme esplicito, oppure puoi passare nel generatore casuale del sistema una volta, oppure puoi usare il IO
per ottenere un nuovo generatore casuale con ogni chiamata. Tutto ciò è possibile senza dover modificare una riga di codice diversa da come viene chiamata in main
, la definizione di computeSomething
non cambia tra questi 3 usi.
È un 'class' non un'implementazione. È possibile ad esempio un puro generatore "casuale" (ad esempio, seme fisso, arg, ...). La finale 'm' monad deciderà la purezza finale. – josejuan
In realtà, il tipo di getRandomR è (MonadRandom m, Random a) => (a, a) -> m a. –
@HonzaPokorny Ho aggiunto alcune modifiche alla mia risposta che potrebbero essere migliori per la tua domanda specifica. – bheklilr