2013-04-16 6 views
6

Ciao gente,Creazione di monadi analoga aperta al IO Monade con lo stato concatenato

Sono abbastanza nuovo a Haskell anche quest'anno (dopo averlo utilizzato nei primi anni 1990 e poi di nuovo nei primi anni 00 del). Sto cercando di scrivere del codice che utilizza un modello che è analoga aperta quasi direttamente al example IO monad shown on the Haskell Wiki:

type IO a = RealWorld -> (a, RealWorld) 

(Sì, so che questo non è l'attuazione GHC di IO, ma semplicemente un veicolo per la comprensione .) La ragione è che nella mia applicazione (un gioco), ora ho due pattern che fanno questo con due diverse sostituzioni dello RealWorld qui. In uno, è lo stato del gioco, e nell'altro, è solo un seme numero casuale StdGen. Io, naturalmente, hanno ora due coppie di tipi come questo:

-- | Easily return a specified value as well as the new random number generator 
type ReturnRNG a = (a, StdGen) 

-- | Take an RNG and return it and another value. 
-- (This is basically like the IO type but with the StdGen instead of RealWorld.) 
type WithRNG a = StdGen -> ReturnRNG a 

-- | Easily return a specified value as well as the new GameState 
type ReturnGS a = (a, GameState) 

-- | Use a GameState and return a value with the updated GameState. 
-- (This is like IO.) 
type WithGS a = GameState -> ReturnGS a 

(Sì, ho potuto li astratto in una coppia con due parametri, ma non ho ottenuto intorno ad esso.) Si può vedere, naturalmente, che i miei tipi e WithRNG a (sinonimi di tipo) sono esattamente analoghi a IO a precedente.

Quindi, ecco un semplice esempio di codice di lavoro effettivo che ora ho:

-- | Returns a random position for the given size. 
randomPos :: (Int, Int)   --^The size 
      -> WithRNG (Int, Int) --^The result (0 up to 1 less than the size) and new RNG seed 
randomPos (w, h) r0 = ((x, y), r2) 
    where 
    (x, r1) = randomR (0, w - 1) r0 
    (y, r2) = randomR (0, h - 1) r1 

Questo crea una coppia casuale in un intervallo specificato, e restituisce il seme RNG finale. Una grande percentuale dei miei metodi è così (usando WithRNG o WithGS), utilizzando uno stato concatenato, a volte anche fino a r4 o r6 (o gs4, ecc.). Preferisco scrivere questo esempio per assomigliare a questo ...

-- (Not working example) 
randomPosSt (w, h) = do 
    x <- randomR (0, w - 1) 
    y <- randomR (0, h - 1) 
    return (x, y) 

... eppure hanno lo stesso identico metodo di firma e semantica. Questo sembra che dovrebbe essere possibile seguendo il tutorial di cui sopra che dà questo esempio:

(>>=) :: IO a -> (a -> IO b) -> IO b 
(action1 >>= action2) world0 = 
    let (a, world1) = action1 world0 
     (b, world2) = action2 a world1 
    in (b, world2) 

Questo, come potete vedere, è quasi esattamente quello che sto facendo in precedenza (una volta che si sostituisce "let" per "where" notazione).

Tuttavia, non posso creare un Monade di un sinonimo di tipo. (Ho provato TypeSynonymInstances ma non sembra funzionare con "instance Monad WithRNG where" o usando un parametro.Utilizzando un newtype sembrerebbe anche aggiungere inutile sintassi brutta.) Non sono stato in grado di capire bene la Monad di stato abbastanza per fare un metodo equivalente usando quello. Anche se avessi avuto successo, tuttavia, l'implementazione della Monad di stato sembrerebbe usare brutti "get" e "put" s (e "runState" ecc.) E rendere il codice inferiore a leggibile, non di più.

-- THIS DOES NOT WORK 
-- | Make a State Monad with random number generator - like WithRNG above 
type RandomState = State StdGen 

-- | Returns a random position for the given size. 
randomPosSt :: (Int, Int)     --^The size 
      -> RandomState (Int, Int) --^The result (0 up to 1 less than the size) and new RNG seed 

Dopo tutto questo, ho concluso che io sono sia facendo qualcosa di sbagliato, incomprensione qualcosa, o semplicemente non posso fare quello che voglio fare. Stavo per dire "beh, non hai davvero bisogno di capire come modificare il tuo codice per far sì che lo stato venga gestito automaticamente, perché funziona bene" e rinunciare e ho pensato che avrei chiedi qui (il mio debutto delirante). Preferirei una soluzione più elegante.

Ho anche figura una soluzione più elegante mi avrebbe dato questa funzione che uso "per libero:"

-- | Maps the specified method, which must take a RNG as the last parameter, 
-- over all the elements of the list, propagating the RNG and returning it. 
-- TODO: Implement this without using recursion? Using a fold? 
mapRandom :: (a -> WithRNG b) --^The function to map (that takes a RNG) 
      -> [a] --^The input list 
      -> WithRNG [b] --^The RNG to return 
mapRandom func [] r0 = ([], r0) 
mapRandom func (x:xs) r0 = (mapped : rest, r2) 
    where 
    (mapped, r1) = func x r0 
    (rest, r2) = mapRandom func xs r1 

Grazie per ogni pensiero, suggestioni, riferimenti e il vostro tempo!

+2

Prima di rispondere, sei a conoscenza della monade 'State'? –

+0

@GabrielGonzalez In effetti ho sentito parlare della monade di Stato: "Non sono stato in grado di capire abbastanza bene la Monade di Stato per fare un metodo equivalente usando quello. Anche se avessi avuto successo, tuttavia, l'implementazione della Monade di Stato sembrerebbe usare brutti "get" e "put" s (e "runStates" ecc.) e rendere il codice meno leggibile, non di più. " –

+1

Prima di rispondere, sei a conoscenza del trasformatore monad ['RandT'] (http://hackage.haskell.org/packages/archive/MonadRandom/0.1.8/doc/html/Control-Monad-Random.html) ? –

risposta

9

È possibile utilizzare il State monade senza usare get o put. Basta avvolgere le funzioni di passaggio di stato direttamente nella State newtype:

import System.Random 

newtype State s a = State { runState :: s -> (a, s) } 

instance Monad (State s) where 
    return a = State (\s -> (a, s)) 

    m >>= f = State (\s0 -> 
     let (a, s1) = runState m s0 
     in runState (f a) s1) 

randomPos :: (Int, Int) -> State StdGen (Int, Int) 
randomPos (w, h) = do 
    x <- State $ randomR (0, w - 1) 
    y <- State $ randomR (0, h - 1) 
    return (x, y) 

Il trucco è quello di osservare il tipo di costruttore State:

State :: (s -> (a, s)) -> State s a 

.. e randomR (lo, hi) ha proprio il tipo giusto per essere avvolto direttamente all'interno State:

randomR (1, 6)   :: StdGen -> (Int, StdGen) 
StateT $ randomR (1, 6) :: State StdGen Int 

Il costruttore State richiede una funzione di passaggio di stato e crea come valore valido per l'uso all'interno di una monade State. Poi, quando si è fatto con la monade è possibile convertire la monade di nuovo alla funzione di stato-passing equivalente utilizzando runState:

runState :: State s a -> (s -> (a, s)) 

runState (randomPos (5, 6)) :: StdGen -> ((Int, Int), StdGen) 

Questo è infatti quanto RandT opere, avvolgendo tutte le funzioni casuali generatore-passando un stato monad, e RandT è equivalente a StateT StdGen sotto il cofano.

Inoltre, come hai detto tu, la formulazione monadica darebbe la versione mappato gratuitamente:

mapRandom 
    :: (a -> (StdGen -> (b , StdGen))) 
    -> ([a] -> (StdGen -> ([b], StdGen))) 
mapRandom f xs = runState $ mapM (State . f) xs 

Questo è perché il tipo di mapM (quando specializzata a State) è:

mapM :: (a -> State s b) -> [a] -> State s [b] 

Pertanto, la funzione mapRandom sopra riportata è costituita da un input in un nuovo tipo State, da utilizzare mapM e da scartare.

+0

Wow. Grazie per la risposta dettagliata. Ho intenzione di provare questo su entrambi i miei tipi e vedere come appare nel mio codice. Ho avuto due domande di follow-up: perché nella tua risposta crei tu stesso un'istanza State newtype e Monad invece di utilizzare un'importazione? E non è possibile evitare i wrapper/unwrapper "State" e "runState" per ottenere qualcosa di più elegante come la (sintetica) elegante sintassi di IO monad? –

+1

@SoftwareEngineer È perché non esiste più un'implementazione ufficiale di una versione non-monade-trasformatore di 'State' nelle librerie standard. Se controlli 'transformers' e' mtl', ora entrambi implementano 'State s' come sinonimo di tipo per' StateT s Identity', e al posto del costruttore 'State' forniscono la funzione' state' che si comporta stessa strada. –

+0

Sì, avevo notato che la monade di Stato era stata modificata. Un'ultima domanda: perché l'IO monade è speciale? Sembra essere implementato esattamente come la monade di stato sopra, ma non ha bisogno di avere il wrapper/wrapper newtype. Mi piacerebbe farla franca in qualche modo. Grazie ancora. (Non so perché non ci vorrà il mio AT Gabriel o AT GabrielGonzalez.) –