2010-10-15 10 views
14

Haskell es un lenguaje de programación puramente funcional.Haskell and State

Mi pregunta es: ¿Cuáles son las ventajas y desventajas de usar Haskell para resolver problemas que involucran muchos estados, por ejemplo, programación de GUI o programación de juegos?

También una pregunta secundaria: ¿qué métodos hay para manejar el estado de una manera funcional?

Gracias de antemano.

+0

http://www.haskell.org/all_about_monads/html/statemonad.html –

+4

El manejo del estado en un lenguaje funcional implica pasar el estado a las funciones. Las mónadas simplifican esto. – tylermac

+0

@tylermac nunca pude entender las mónadas, bueno, no puedo decir que soy estúpido, al menos soy BS en CS, pero las mónadas ... ¿conoces buenos tutoriales? – Andrey

Respuesta

17

voy a responder a su segunda pregunta. En realidad, hay muchas maneras de manejar el estado mutable en Haskell (y otros lenguajes FP). En primer lugar, Haskell soporta el estado mutable en IO, mediante construcciones IORef y mvar. Usar estos se sentirá muy familiar para los programadores de idiomas imperativos. También hay versiones especializadas como STRef y TMVar, así como matrices mutables, punteros y otros datos variables. El mayor inconveniente es que generalmente solo están disponibles dentro de IO o en una mónada más especializada.

La forma más común de simular el estado en un lenguaje funcional es explícitamente pasar el estado como un argumento de función y el valor devuelto. Por ejemplo:

randomGen :: Seed -> (Int, Seed) 

Aquí randomGen toma un parámetro de semillas y devuelve una nueva semilla. Cada vez que lo llamas, necesitas hacer un seguimiento de la semilla para la próxima iteración. Esta técnica está siempre disponible para aprobar el estado, pero rápidamente se vuelve tediosa.

Probablemente el método más común de Haskell es utilizar una mónada para encapsular este estado. Podemos reemplazar randomGen con esto:

-- a Random monad is simply a Seed value as state 
type Random a = State Seed a 

randomGen2 :: Random Int 
randomGen2 = do 
    seed <- get 
    let (x,seed') = randomGen seed 
    put seed' 
    return x 

Ahora las funciones que necesitan un PRNG se puede ejecutar dentro de la mónada aleatoria para solicitar cuando sea necesario. Solo necesita proporcionar un estado inicial y el cálculo.

runRandomComputation :: Random a -> Seed -> a 
runRandomComputation = evalState 

(tenga en cuenta que hay funciones que acortan considerablemente la definición de randomGen2; elegí la versión más explícita).

Si su cálculo aleatorio también necesita acceso a IO, entonces utiliza la versión del transformador de mónada State, StateT.

De nota especial es la mónada ST, que esencialmente proporciona un mecanismo para encapsular las mutaciones IO-específicas lejos del resto de IO. La mónada ST proporciona STRefs, que son una referencia mutable a datos, y también matrices mutables.El uso de ST, es posible definir cosas como esta:

randomList :: Seed -> [Int] 

donde [Int] es una lista infinita de números aleatorios (que va de ciclo con el tiempo dependiendo de su PSRG) a partir de la semilla de partida que le des.

Finalmente, está Functional Reactive Programming. Probablemente las bibliotecas actuales más destacadas para esto sean Yampa y Reactive, pero las otras también valen la pena mirar. Hay varias aproximaciones al estado mutable dentro de las diversas implementaciones de FRP; por mi ligero uso de ellos, a menudo parecen similares en concepto a un marco de señalización como en QT o Gtk + (por ejemplo, agregar oyentes para eventos).

Ahora, para la primera pregunta. Para mí, la mayor ventaja es que el estado mutable está separado de otro código en el nivel de tipo. Esto significa que el código no puede modificar accidentalmente el estado a menos que se mencione explícitamente en la firma de tipo. También ofrece un muy buen control del estado de solo lectura frente al estado mutable (Mónada Reader vs. Mónada State). Encuentro muy útil estructurar mi código de esta manera, y es útil poder decir simplemente desde la firma de tipo si una función podría estar mutando inesperadamente.

Personalmente, realmente no tengo ninguna reserva sobre el uso del estado mutable en Haskell. La mayor dificultad es que puede ser tedioso agregar estado a algo que no lo necesitaba anteriormente, pero lo mismo sería tedioso en otros idiomas que he usado para tareas similares (C#, Python).

+0

¿Qué es 'Semilla' aquí? Intenté definirlo como int en la parte superior de mi archivo ('type Seed = Int') pero obtuve el error' No instance for (Show (Random Int)) '(Inicialmente usé la teoría de tu código, que no trabajo, así que intenté copiar y pegar todo y obtuve este error). –

+0

Esto es más pseudocódigo que una implementación real. 'Seed' es un tipo abstracto que representa los datos dependientes del estado del PRNG. Estaba usando PNRG porque es un ejemplo bien conocido de una clase de algoritmos con estado. Si necesita un generador de números aleatorios, recomendaría un paquete como 'mwc-random' o' mersenne-random'. –

+0

Gracias por la respuesta. Estoy tratando de entender realmente los modos en lugar de usar un PRNG, pero creo que este es un buen ejemplo. Estoy totalmente atrapado. Hice una pregunta ASÍ sobre esto hace un par de horas, ¡pero ahora estoy aún más confundido! http://stackoverflow.com/questions/23595363/simple-haskell-monad-random-number/ –

2

Normalmente lo que haría sería usar un Monad Transformer con un StateT y un IO, esto porque la vista (GUI) necesita IO para responder, sin embargo una vez que definió su Monad Transformer en un newtype, le gustaría haga las firmas de la lógica del juego solo con la interfaz MonadState, de esa manera usted todavía tiene el beneficio de los cambios que no son IO-ness. Código de abajo para explicar lo que quiero decir:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 
import Control.Monad.State 

data GameState = GameState { ... } deriving (Show) 
newtype GameMonad a = GameMonad (StateT GameState IO a) 
         deriving (Monad, MonadState GameState, MonadIO) 

-- This way, now you have a monad with the state of the info 
-- what would you like now is being able to modify the state, without actually 
-- having IO capabilites. 

movePlayerOnState :: (MonadState GameState) m => Position -> m() 
-- In this function we get the state out of the state monad, and then we modify 
-- with a pure function, then put the result back again 

-- Other times you would like to have the GUI API available, requiring the IO monad 
-- for that 
renderGameFromState :: MonadIO m => GameState -> m() 
-- in this method you would use liftIO method to call the GUI API 

Este código es bastante complejo si no entiende mónadas, pero mi regla de oro es, averiguar lo que la mónada es para el Estado, entiende lo Monad Los transformadores son (sin la necesidad de entender cómo funcionan) y cómo usar la mónada StateT.

Te puedo apuntar a un proyecto Sokoban que hice con otro compañero de equipo que podría ser útil, se utiliza ncurses como la interfaz gráfica de usuario, pero puede hacerse una idea de la lógica y la forma en que logró estados en el juego

http://github.com/roman/HaskBan

Buena suerte.

4

¿Cuáles son las ventajas y desventajas de usar Haskell para resolver problemas que involucran muchos estados, por ejemplo, programación de GUI o programación de juegos?

La ventaja es que, incluso si usted no está tomando ventaja particular de la pureza, Haskell es simplemente un buen lenguaje.

Funciones de primera clase: no debería ser un gran problema en 2010, pero lo es. Tipos algebraicos con coincidencia de patrones. Potente comprobación de tipo estático con inferencia de tipo. Sintaxis limpia Simultaneidad de primera clase, STM y paralelismo puro sin hilos. Un buen compilador Toneladas de bibliotecas y más todos los días. Una comunidad activa y servicial.

Estas no son grandes decisiones ideológicas como la pureza o la pereza. Son solo buenas ideas.Son cosas que la mayoría de los idiomas podrían tener, pero muchos no.

+0

¿Podría decirme qué se entiende por concurrencia de primera clase? –

4

Una mónada de estado es casi la peor forma de modelar una GUI o un juego en Haskell. Creo que la segunda mejor opción es usar concurrencia en ambos casos. Sin embargo, la mejor opción ha sido mencionada por Paul: Functionalreactive programming (FRP).

Personalmente defiendo el FRP con flecha (AFRP), que creo que se implementó por primera vez como Yampa y luego se bifurcó como el Animas ligeramente más útil. Sin embargo, Yampa rápidamente alcanza su límite, así que he escrito una biblioteca más poderosa y más expresiva llamada netwire, que también tiene algunas mejoras conceptuales sobre las dos anteriores.

En esencia, AFRP es un sistema de estado funcional. Es funcional en ese estado no se modela como el cambio de valores variables, sino como funciones mutantes. Esto es más limpio y no requiere ninguna programación imperativa como las mónadas estatales.