3 votes

runState dans une fonction monadique d'état ne fonctionne pas

J'essaie de résoudre le problème 2.8 du livre "AI - A Modern Approach" qui implique une grille de cellules et le choix de mouvements aléatoires pour naviguer dans la grille.

2.7 Mettre en place un environnement pour une pièce rectangulaire n X m, où chaque carré a 5 % de chances de contenir de la saleté et où n et m sont des nombres entiers. carré a 5 % de chances de contenir de la saleté, et n et m sont choisis au aléatoirement entre 8 et 15 inclus.

2.8 Concevoir et mettre en œuvre un agent réflexe pur pour l'environnement de l'exercice 2.7. l'exercice 2.7, sans tenir compte de l'obligation de rentrer chez soi, et mesurez ses ses performances.

J'ai donc utilisé deux monades d'état - une avec Grid comme état et un autre avec StdGen comme l'État. Le code se compile sans aucune erreur mais lorsque je l'exécute depuis GHCi, il se bloque et ne revient pas.

La partie pertinente du code :

Code de soutien

type RandomState = State StdGen

makeGrid :: (Int, Int) -> (Int, Int) -> Float -> RandomState Grid

doAction :: Action -> Cleaner -> State Grid Cleaner

getRandomR :: Random a => (a, a) -> RandomState a
getRandomR limits = do
  gen <- get
  let (val, gen') = randomR limits gen
  put gen'
  return val

chooseAction :: Percepts -> RandomState Action
chooseAction percepts
  | PhotoSensor `elem` percepts = return SuckDirt
  | InfraredSensor `elem` percepts = return TurnOff
  | TouchSensor `elem` percepts = return TurnLeft
  | otherwise = do
    r <- getRandomR ((1, 3) :: (Int, Int))
    case r of
      1 -> return GoForward
      2 -> return TurnRight
      3 -> return TurnLeft

Code principal

runCleaner :: Int -> Cleaner -> StateT Grid RandomState Cleaner
runCleaner turnsLeft cleaner@(Cleaner _ _ _ ph _) =
  if turnsLeft == 0
    then return cleaner
    else do
      grid <- get
      gen <- lift $ get
      cleaner <- case ph of
        [] -> do
          let (cleaner, grid) = runState (doAction GoForward cleaner) grid
          put grid
          return cleaner
        _ -> do
          let (action, gen) = runState (chooseAction (head ph)) gen
          lift $ put gen

          let (cleaner, grid) = runState (doAction action cleaner) grid
          put grid
          return cleaner

      case clState cleaner of
        Off -> return cleaner
        On -> runCleaner (turnsLeft - 1) cleaner

simulateOnGrid :: Int -> Grid -> StdGen -> (Cleaner, Grid)
simulateOnGrid maxTurns grid gen = 
  evalState (runStateT (runCleaner maxTurns cleaner) grid) gen
  where cleaner = createCleaner (fromJust $ cell (0,0) grid) East

J'invoque le simulateOnGrid de GHCi comme ceci :

> gen <- newStdGen
> let grid = evalState (makeGrid (8,15) (8,15) 0.05) gen
> simulateOnGrid 5 grid gen

et le code reste bloqué à la ligne :

let (cleaner, grid) = runState (doAction GoForward cleaner) grid

ce que j'ai confirmé en mettant des traces dans le code. L'appel au doAction ne se produit jamais.

Le problème semble être l'utilisation de runState à l'intérieur de la runCleaner mais je ne parviens pas à en trouver la raison.

Veuillez expliquer la raison et s'il existe un moyen de résoudre ce problème.

De même, l'utilisation de runState à l'intérieur d'une fonction monadique me semble erronée. Veuillez suggérer s'il y a une meilleure façon de le faire.

6voto

hammar Points 89293

Dans la partie droite d'un let les noms liés sont dans le champ d'application, donc lorsque vous écrivez

let (cleaner, grid) = runState (doAction GoForward cleaner) grid

le site cleaner y grid sur le côté droit de la = sont les mêmes que ceux de la partie gauche. Cela provoquera probablement une boucle infinie puisque vous réinjectez la sortie de l'action comme entrée ! Pour éviter cela, utilisez des noms différents pour la sortie.

let (cleaner', grid') = runState (doAction GoForward cleaner) grid

Cela mis à part, vous avez tout à fait raison de dire qu'utiliser runState comme ça, c'est bizarre. Je pense que vous pouvez simplifier grandement les choses si vous changez le type de doAction à

doAction :: Monad m => Action -> Cleaner -> StateT Grid m Cleaner

Vous n'avez pas fourni le corps de cette fonction, mais je suppose qu'elle fonctionnera toujours avec cette signature de type moins contraignante.

Maintenant, vous n'avez plus à vous occuper de récupérer et de mettre l'état manuellement, car doAction peut être exécuté directement dans votre monade, et chooseAction peut être exécuté en le soulevant d'abord. En utilisant ceci, votre case L'expression peut être écrite de manière beaucoup plus succincte :

cleaner <- case ph of
    [] -> doAction GoForward cleaner
    _  -> do action <- lift $ chooseAction (head ph)
             doAction action cleaner

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X