2011-01-24 19 views
9

Sto cercando di scrivere un modulo generico che permetta ai programmi Haskell di interagire con Cassandra. Il modulo dovrà mantenere il proprio stato. Ad esempio, avrà un pool di connessioni e un elenco di callback da richiamare quando viene salvato un nuovo record. Come dovrei strutturare il codice in modo che questo modulo possa mantenere il suo stato? Ecco alcuni degli approcci che ho preso in considerazione. Sono sulla buona strada? (Sono nuovo di Haskell e ancora imparando il modo migliore per pensare in modo funzionale.)Come strutturate un modulo stateful in Haskell?

Opzione 1:

Il modulo viene eseguito in un (StateT s IO) monade, dove s è lo stato globale per l'intero programma usando il modulo Cassandra. Naturalmente, poiché il modulo Cassandra può essere utilizzato da più programmi, i dettagli di ciò che è in s dovrebbero essere invisibili al modulo Cassandra. Il modulo dovrebbe esportare una classe di caratteri che gli consenta di estrarre CassandraState da s e reinserire un nuovo CassandraState in s. Quindi, qualsiasi programma che utilizza il modulo dovrebbe rendere il suo stato principale un membro di questa classe di tipi.

Opzione 2:

Il modulo viene eseguito in un (StateT CassandraState IO) monade. Ogni volta che qualcuno chiama un'azione nel modulo, dovrebbe estrarre il CassandraState da qualsiasi posizione in cui lo hanno nascosto, richiamare l'azione con runState e prendere lo stato risultante e riporlo nuovamente (ovunque).

Opzione 3:

Non mettere le funzioni del modulo Cassandra in una monade StateT a tutti. Invece, chiedi al chiamante di passare esplicitamente in CassandraState quando è necessario. Il problema con l'opzione 2 è che non tutte le funzioni nel modulo modificheranno lo stato. Ad esempio, l'ottenimento di una connessione modificherà lo stato e richiederà al chiamante di bloccare lo stato risultante. Tuttavia, per salvare un nuovo record è necessario leggere lo stato (per ottenere i callback), ma non è necessario modificare lo stato. L'opzione 2 non fornisce al chiamante alcun suggerimento che connetta cambi lo stato mentre crea no.

Ma, se mi allontano dall'uso della monade StateT e ho solo funzioni che contengono gli stati come parametri e restituiscono valori semplici o tuple di valori semplici e nuovi stati, allora è davvero ovvio per il chiamante quando lo stato ha bisogno da salvare. (Sotto le copertine del mio modulo, prenderei gli stati in arrivo e li costruirò in una monade (StateT CassandraState IO), ma i dettagli di questo sarebbero nascosti al chiamante.Quindi, per il chiamante, l'interfaccia è molto esplicita , ma sotto le coperte, è solo Opzione 2)

Opzione 4:

Qualcos'altro?

Questo problema deve verificarsi molto spesso quando si creano moduli riutilizzabili. Esiste una sorta di metodo standard per risolverlo?

(A proposito, se qualcuno conosce un modo migliore per interagire con Cassandra da Haskell che usare Thrift, per favore fatemelo sapere! Forse non devo scrivere questo a tutti. :-)

+2

FYI - in 'modulo' cerchi Haskell è l'unità di compilazione, cioè un singolo file sorgente. Anche se in realtà non ho un nome migliore per quello che stai descrivendo, parlare di un "modulo" che ha stato mi ha buttato per un momento. –

+0

Oops. Avrei dovuto dire "pacchetto" o "biblioteca". –

risposta

9

Qualcosa come il modello HDBC dovrebbe avere un tipo di dati CassandraConnection esplicito. Ha un MVar interno con uno stato mutabile. Dal momento che tutte le tue azioni sono in IO comunque, immagino, possono semplicemente prendere CassandraConnection come argomento per queste azioni. L'utente può quindi comprimere quella connessione in uno stato o lettore monade, o collegarlo in modo esplicito o fare ciò che vuole.

Internamente è possibile utilizzare una monade o meno - questa è davvero la vostra chiamata. Tuttavia, preferisco le API che, quando possibile, non costringono gli utenti a una particolare monade, a meno che non sia veramente necessario.

Quindi questa è una sorta di versione dell'opzione 3. Ma l'utente non dovrebbe davvero preoccuparsi se sta cambiando o meno lo stato della connessione - a quel livello si può davvero nascondere i dettagli da loro.

+0

Ho appena trovato questa altra domanda parlando di unire le connessioni HDBC usando MVar. Penso che stia illustrando quello che stai suggerendo. http://stackoverflow.com/questions/1141677/concurrent-db-connection-pool-in-haskell –

+0

Se, inoltre, si strutturano tutti i tipi di funzioni in modo che terminino in '... -> CassandraConnection -> IO a ', gli utenti possono anche ottenere facilmente la stessa astrazione dell'opzione 2 tramite il trasformatore monad' ReaderT'. Possono semplicemente racchiudere le funzioni della libreria nel costruttore 'ReaderT'. E.G., 'foo :: A -> B -> CassandraConnection -> IO C' è impacchettato come:' myFoo a b = ReaderT (foo a b) '. – mokus

+0

@Clint sembra essere simile. Fondamentalmente vuoi un handle astratto per una risorsa, con operazioni esplicite per connetterti, disconnetterti, ecc. – sclv

3

I andiamo con l'opzione 2. Gli utenti del tuo modulo non dovrebbero usare direttamente runState; al contrario, è necessario fornire un tipo opaco Cassandra con un'istanza della classe di tipi Monad e un'operazione runCassandra :: Cassandra a -> IO a per "escape" Cassandra. Le operazioni esportate dal modulo dovrebbero tutte essere eseguite nella monade Cassandra (ad esempio doSomethingInterestingInCassandra :: Int -> Bool -> Cassandra Char) e la loro definizione può accedere allo CassandraState impacchettato.

Se gli utenti necessitano di uno stato aggiuntivo per la loro applicazione, possono sempre avvolgere un trasformatore monade intorno a Cassandra, ad es. StateT MyState Cassandra.

+2

Grazie per la risposta. Supponiamo che un'applicazione non stia semplicemente usando il mio modulo Cassandra, ma stia usando anche un numero di altri moduli stateful. Supponiamo anche che l'applicazione debba definire il proprio stato (MyState) ed eseguire nella monade IO. Ti ritroverai con un brutto set di StateT incorporato in StateT e brutte sequenze di sollevamento per ottenere qualcosa da esso? Cosa succede quando l'applicazione inizia ad usare un nuovo modulo stateful o smette di usare un modulo stateful? Questo rovina tutto lo stato annidato e il sollevamento? –

Problemi correlati