2011-01-24 12 views
9

Estoy buscando escribir un módulo genérico que permita a los programas de Haskell interactuar con Cassandra. El módulo necesitará mantener su propio estado. Por ejemplo, tendrá un grupo de conexiones y una lista de devoluciones de llamadas que se invocarán cuando se guarde un nuevo registro. ¿Cómo debería estructurar el código para que este módulo pueda mantener su estado? Estos son algunos de los enfoques que he estado considerando. ¿Estoy en el camino correcto? (Soy nuevo en Haskell y todavía está aprendiendo las mejores maneras de que funcionalmente.)¿Cómo se estructura un módulo con estado en Haskell?

Opción 1:

El módulo se ejecuta en una mónada (s Statet IO), donde s es el estado global de la totalidad programa usando el módulo Cassandra. Por supuesto, dado que el módulo de Cassandra podría ser utilizado por múltiples programas, los detalles de lo que hay en s deberían ser invisibles para el módulo de Cassandra. El módulo tendría que exportar una clase de tipo que le permitiera extraer CassandraState de s y volver a insertar un nuevo CassandraState en s. Entonces, cualquier programa que use el módulo debería hacer que su estado principal sea miembro de esta clase de tipos.

Opción 2:

El módulo se ejecuta en una mónada (Statet CassandraState IO). Cada vez que alguien invoca una acción en el módulo, debe extraer CassandraState de donde sea que esté escondido, invocar la acción con runState, tomar el estado resultante y esconderlo de nuevo (donde sea).

Opción 3:

No ponga las funciones del módulo de Cassandra en una mónada Statet en absoluto. En su lugar, haga que la persona que llama pase explícitamente en CassandraState cuando sea necesario. El problema con la opción 2 es que no todas las funciones en el módulo modificarán el estado. Por ejemplo, obtener una conexión modificará el estado y requerirá que la persona que llama oculte el estado resultante. Pero, para guardar un nuevo registro, es necesario leer el estado (para obtener las devoluciones de llamada), pero no es necesario cambiar el estado. La opción 2 no le da a la persona que llama ninguna pista de que la conexión cambia el estado mientras que crear no.

Pero, si dejo de usar la mónada StateT y solo tengo funciones que toman en estados como parámetros y devuelven valores simples o tuplas de valores simples y nuevos estados, entonces es realmente obvio para quien llama cuando el estado lo necesita para ser salvado (Bajo las cubiertas de mi módulo, tomaría los estados entrantes y los construiría en una mónada (StateT CassandraState IO), pero los detalles de esto quedarían ocultos para la persona que llama. Por lo tanto, para la persona que llama, la interfaz es muy explícita. , pero bajo las sábanas, es sólo la opción 2.)

Opción 4:

Algo más?

Este problema debe aparecer con bastante frecuencia cuando se construyen módulos reutilizables. ¿Hay algún tipo de forma estándar para resolverlo?

(Por cierto, si alguien sabe una mejor manera de interactuar con Cassandra de Haskell que el uso de Ahorro, por favor hágamelo saber! Tal vez no tengo que escribir esto en absoluto. :-)

+2

FYI - en Haskell circles 'module' es la unidad de compilación, es decir, un único archivo fuente. Aunque realmente no tengo un nombre mejor para lo que describes, hablar de un "módulo" con estado me echó por un momento. –

+0

Vaya. Debería haber dicho 'paquete' o 'biblioteca'. –

Respuesta

9

Algo así como el modelo HDBC sería tener un tipo de datos CassandraConnection explícito. Tiene un MVar adentro con algún estado mutable. Como todas tus acciones están en IO de todos modos, me imagino que pueden tomar CassandraConnection como argumento para estas acciones. El usuario puede empacar esa conexión en una mónada de estado o lector, o enhebrarla explícitamente, o hacer lo que quiera.

Internamente puede usar una mónada o no, esa es realmente su llamada. Sin embargo, estoy a favor de las API que, cuando sea posible, no obligan a los usuarios a una mónada en particular, a menos que sea realmente necesario.

Este es un tipo de versión de la opción 3. Pero al usuario no le debería importar si están cambiando o no el estado de la conexión, en ese nivel realmente puede ocultar los detalles de ellos.

+0

Acabo de encontrar esta otra pregunta hablando sobre la agrupación de conexiones HDBC utilizando MVar. Creo que está ilustrando lo que estás sugiriendo. http://stackoverflow.com/questions/1141677/concurrent-db-connection-pool-in-haskell –

+0

Si, además, estructura todos los tipos de sus funciones para que finalicen en '... -> CassandraConnection -> IO a', sus usuarios también pueden obtener con extrema facilidad la misma abstracción que en la opción 2 a través del transformador de mónada 'ReaderT'. Simplemente pueden ajustar las funciones de la biblioteca en el constructor 'ReaderT'. E.G., 'foo :: A -> B -> CassandraConnection -> IO C' se envuelve como:' myFoo a b = ReaderT (foo a b) '. – mokus

+0

@Clint parece ser similar. Básicamente, quieres un manejo abstracto de un recurso, con operaciones explícitas para conectar, desconectar, etc. – sclv

3

I iría con la Opción 2. Los usuarios de su módulo no deberían usar runState directamente; en su lugar, debe proporcionar un tipo opaco Cassandra con una instancia de la clase de tipo Monad y alguna operación runCassandra :: Cassandra a -> IO a para "escapar" de Cassandra. Las operaciones exportadas por su módulo deben ejecutarse todas en la mónada Cassandra (por ejemplo, doSomethingInterestingInCassandra :: Int -> Bool -> Cassandra Char), y su definición puede tener acceso al CassandraState envuelto.

Si sus usuarios necesitan algún estado adicional para su aplicación, siempre pueden envolver un transformador de mónada alrededor de Cassandra, p. StateT MyState Cassandra.

+2

Gracias por la respuesta. Supongamos que una aplicación no solo está usando mi módulo Cassandra, sino que también está usando un número de otros módulos con estado. Supongamos también que la aplicación necesita definir su propio estado (MyState) y ejecutar en la mónada IO. ¿Terminas con un desagradable conjunto de StateT incrustado dentro de StateT's, y desagradables secuencias de levantamiento para sacar algo de eso? ¿Qué sucede en el futuro cuando la aplicación comienza a usar un nuevo módulo con estado o deja de usar un módulo con estado? ¿Eso arruina todos los StateT anidados y el levantamiento? –

Cuestiones relacionadas