2012-06-02 19 views
9

Estoy tratando de escribir un pequeño juego en Haskell, y hay una buena cantidad de estado necesario para pasar. Quiero tratar de ocultar el estado con la mónada de estadoUsando la mónada de estado para ocultar el estado explícito

Ahora me he encontrado con un problema: las funciones que toman el estado y un argumento eran fáciles de escribir para trabajar en la mónada de estado. Pero también hay funciones que simplemente toman el estado como argumento (y devuelven un estado modificado, o posiblemente algo más).

En una parte de mi código, tengo esta línea:

let player = getCurrentPlayer state 

me gustaría que no toma estado, y en lugar de escribir

player <- getCurrentPlayerM 

Actualmente, su puesta en práctica se parece a esto

getCurrentPlayer gameState = 
    (players gameState) ! (on_turn gameState) 

y parecía lo suficientemente simple como para que funcione en la mónada de estado escribiéndola así:

getCurrentPlayerM = do state <- get 
         return (players state ! on_turn state) 

Sin embargo, eso provoca quejas de ghc! No hay instancia para (MonadState GameState m0) que surja de un uso de `get ', dice. Yo ya había vuelto a escribir una función muy similar, excepto que no se nularia en su forma mónada Estado, por lo que en una corazonada, Reescribí así:

getCurrentPlayerM _ = do state <- get 
         return (players state ! on_turn state) 

y, efectivamente, funciona! Pero, por supuesto, tengo que llamarlo getCurrentPlayerM(), y me siento un poco tonto al hacerlo. ¡Pasar una discusión era lo que quería evitar en primer lugar!

Una sorpresa adicional: mirando su tipo en ghci consigo

getCurrentPlayerM :: MonadState GameState m => t -> m P.Player 

pero si trato de establecer que de forma explícita en mi código, me sale otro error: "El argumento para no tipo variable en la restricción MonadState GameState m "y una oferta de extensión de idioma para permitirlo. Supongo que es porque mi GameState es un tipo y no una clase de tipo, pero por qué se acepta en la práctica, pero no cuando trato de ser explícito al respecto. Estoy más confundido.

Para resumir:

  1. Por qué no puedo escribir funciones nullary en la mónada Estado?
  2. ¿Por qué no puedo declarar el tipo que tiene mi función de solución alternativa?

Respuesta

14

El problema es que no escribe firmas de tipo para sus funciones, y se aplica la restricción de monomorfismo.

Cuando se escribe:

getCurrentPlayerM = ... 

está escribiendo un valor unario definición restringida de nivel superior sin una declaración de tipo, por lo que el compilador de Haskell tratará de inferir un tipo de definición. Sin embargo, la restricción de monomorfismo (Literalmente: restricción de forma única) establece que todas las definiciones de nivel superior con restricciones de tipo inferido tienen que resolverse en tipos concretos, es decir, no deben ser polimórficos.


Para explicar lo que quiero decir, tomar este ejemplo sencillo:

pi = 3.14 

Aquí, definimos pi sin un tipo, por lo GHC infiere el tipo Fractional a => a, es decir, "cualquier tipo a, siempre y cuando se puede tratar como una fracción ". Sin embargo, este tipo es problemático, porque significa que pi no es una constante, aunque parezca que sí lo es. ¿Por qué? Porque el valor de pi se volverá a calcular según el tipo que queramos que sea.

Si tenemos (2::Double) + pi, pi será un Double. Si tenemos (3::Float) + pi, pi será un Float. Cada vez que se utiliza pi, debe volver a calcularse (porque no podemos almacenar versiones alternativas de pi para todos posibles tipos fraccionarios, ¿verdad?). Esto está bien para el simple literal 3.14, pero ¿y si quisiéramos más decimales de pi y usáramos un sofisticado algoritmo que lo calculó? No deseamos que se vuelva a calcular cada vez que se use pi, ¿o sí?

Por este motivo, el informe Haskell indica que las definiciones unarias restringidas por tipos de nivel superior deben tener un único tipo (monomórfico) para evitar este problema. En este caso, pi obtendría el tipo default de Double. Puede cambiar el valor predeterminado tipos numéricos si usted quiere, usando default palabra clave:

default (Int, Float) 

pi = 3.14 -- pi will now be Float 

En su caso, sin embargo, de que está recibiendo la firma inferido:

getCurrentPlayerM :: MonadState GameState m => m P.Player 

Significa: "Para cualquier Estado mónada que almacena GameState s, recupera un jugador. " Sin embargo, debido a que se aplica la restricción de monomorfismo, Haskell se ve obligado a intentar que este tipo no sea polimórfico, al elegir un tipo concreto para m. Sin embargo, no puede encontrar uno, porque no hay ningún tipo de incumplimiento para las mónadas de estado, como hay para los números, por lo que se da por vencido.

O se quiere dar a su función de un tipo explícito firma:

getCurrentPlayerM :: MonadState GameState m => m P.Player 

... pero usted tendrá que añadir la extensión de la lengua FlexibleContexts Haskell para que funcione, añadiendo esta en la parte superior de su archivo:

{-# LANGUAGE FlexibleContexts #-} 

O, puede especificar explícitamente qué mónada estado que desea:

getCurrentPlayerM :: State GameState P.Player 

También puede desactivar la restricción de monomorfismo, agregando la extensión para eso; Sin embargo, es mucho mejor agregar firmas de tipo.

{-# LANGUAGE NoMonomorphismRestriction #-} 

PS. Si usted tiene una función que toma su estado como un parámetro, que puede utilizar:

value <- gets getCurrentPlayer 

También debe mirar en el uso Lenses con State monads, lo que le permite escribir código muy limpio para pasar del estado implícito.

+1

Explicar explícitamente qué mónada quiero parece ser la solución correcta en mi caso. Sí, ya me encontré con el problema con los registros dentro de los registros, y leí un poco sobre Lentes (entre otras cosas, cuando busqué la respuesta aquí, ¡parece que me recomiendan mucho!). Gracias, excelentes explicaciones! –

Cuestiones relacionadas