2017-10-18 5 views
3

J'écris du code (autour des stratégies de jeu de cartes) qui utilise State et la récursivité ensemble. Peut-être que cette partie n'a pas besoin de réellement (elle me semble déjà maladroite, même en tant que débutant relatif), mais il y a d'autres parties qui font probablement ma question générale ...MonadRandom, transformateurs d'état et Monad

Ma première implémentation naïve est entièrement déterministe (le choix de l'offre est tout simplement la première option offerte par la fonction validBids):

bidOnRound :: (DealerRules d) => d -> NumCards -> State ([Player], PlayerBids)() 
bidOnRound dealerRules cardsThisRound = do 
    (players, bidsSoFar) <- get 
    unless (List.null players) $ do 
    let options = validBids dealerRules cardsThisRound bidsSoFar 
    let newBid = List.head $ Set.toList options 
    let p : ps = players 
    put (ps, bidsSoFar ++ [(p, newBid)]) 
    bidOnRound dealerRules cardsThisRound 

Et je l'appelle de:

playGame :: (DealerRules d, ScorerRules s) => d -> s -> StateT Results IO() 
    ... 
let (_, bidResults) = execState (bidOnRound dealerRules cardsThisRound) (NonEmpty.toList players, []) 

maintenant, je suis conscient que je dois apporter aléatoire dans cela et plusieurs autres parties de la code. Ne voulant pas jeter IO partout, ni passer des graines aléatoires manuellement tout le temps, je pense que je devrais utiliser MonadRandomou quelque chose. Une bibliothèque que j'utilise l'utilise à bon escient. Est-ce un choix judicieux?

Voici ce que j'ai essayé:

bidOnRound :: (DealerRules d, RandomGen g) => d -> NumCards -> RandT g (State ([Player], PlayerBids))() 
bidOnRound dealerRules cardsThisRound = do 
    (players, bidsSoFar) <- get 
    unless (List.null players) $ do 
    let options = Set.toList $ validBids dealerRules cardsThisRound bidsSoFar 
    rnd <- getRandomR (0 :: Int, len options - 1) 
    let newBid = options List.!! rnd 
    let p : ps = players 
    put (ps, bidsSoFar ++ [(p, newBid)]) 
    bidOnRound dealerRules cardsThisRound 

mais je suis mal à l'aise déjà, plus peut ne pas fonctionner comment appeler cela, par exemple en utilisant evalRand en combinaison avec execState etc. Plus je lis sur MonadRandom, RandGen et mtl vs autres, moins sûr que je suis ce que je fais ...

Comment dois-je combiner soigneusement et State Randomness et comment faire Je les appelle correctement?

Merci! EDIT: pour référence, full current source on Github.

Répondre

7

Eh bien, que diriez-vous d'un exemple pour vous aider. Puisque vous ne publiez pas un extrait de code de travail complet, je vais juste remplacer un grand nombre de vos opérations et montrer comment les monades peuvent être évalués:

import Control.Monad.Trans.State 
import Control.Monad.Random 
import System.Random.TF 

bidOnRound :: (RandomGen g) => Int -> RandT g (State ([Int], Int))() 
bidOnRound i = 
do rand <- getRandomR (10,20) 
    s <- lift $ get 
    lift $ put ([], i + rand + snd s) 

main :: IO() 
main = 
do g <- newTFGen 
    print $ flip execState ([],1000) $ evalRandT (bidOnRound 100) g 

La chose à noter ici est que vous « Déballer » la monade extérieure premier. Donc, si vous avez RandT (StateT Reader ...) ... alors vous exécutez RandT (ex via evalRandT ou similaire) puis l'état puis le lecteur. Deuxièmement, vous devez lift de la monade externe pour utiliser les opérations sur la monade intérieure. Cela peut sembler maladroit et c'est parce que c'est horriblement maladroit. Les meilleurs développeurs que je connaisse - ceux dont je regarde et travaille avec le code - extraient les opérations de monad et fournissent une API avec toutes les primitives terminées, donc je n'ai pas besoin de penser à la structure de la monade pendant que je '' Je pense à la structure de la logique que j'écris.

Dans ce cas (il sera légèrement parvins que j'ai écrit ci-dessus sans aucun domaine d'application, la rime ou la raison), vous pouvez écrire:

type MyMonad a = RandT TFGen (State ([Int],Int)) a 

runMyMonad :: MyMonad() -> IO Int 
runMyMonad f = 
do g <- newTFGen 
    pure $ snd $ flip execState ([],1000) $ evalRandT f g 

Avec le Monad défini comme un alias simple et l'opération d'exécution du les fonctions de base sont plus faciles:

flipCoin :: MyMonad Int 
flipCoin = getRandomR (10,20) 

getBaseValue :: MyMonad Int 
getBaseValue = snd <$> lift get 

setBaseValue :: Int -> MyMonad() 
setBaseValue v = lift $ state $ \s -> ((),(fst s, v)) 

Avec cette jambe-travail hors de la voie, ce qui est généralement une partie mineure de faire une application réelle, le domaine logique spécifique est plus facile d'écrire et certainement plus facile à lire:

bidOnRound2 :: Int -> MyMonad() 
bidOnRound2 i = 
do rand <- flipCoin 
    old <- getBaseValue 
    setBaseValue (i + rand + old) 

main2 :: IO() 
main2 = print =<< runMyMonad (bidOnRound2 100) 
+0

Wow, c'est génial, merci. Je vais y travailler correctement ce soir. J'aurais dû faire un lien avec la source Github actuelle ... Je vais le faire maintenant. – declension