2010-03-05 6 views
15

Sto pensando ai modi in cui utilizzare il sistema di tipi Haskell per rafforzare la modularità in un programma. Ad esempio, se ho un'applicazione web, sono curioso di sapere se esiste un modo per separare tutto il codice del database dal codice CGI dal codice del filesystem dal codice puro.Utilizzo del sistema di tipo Haskell per rafforzare la modularità

Per esempio, io sto immaginando una monade di DB, così ho potuto scrivere funzioni come:

countOfUsers :: DB Int 
countOfUsers = select "count(*) from users" 

vorrei che fosse impossibile usare gli effetti collaterali diversi da quelli supportati dalla monade di DB. Sto immaginando una monade di livello superiore che sarebbe limitata ai gestori di URL diretti e che sarebbe in grado di comporre chiamate alla monade DB e alla monade IO.

È possibile? È saggio?

Aggiornamento: ho finito per il raggiungimento di questo con la Scala, invece di Haskell: http://moreindirection.blogspot.com/2011/08/implicit-environment-pattern.html

risposta

13

sto immaginando una monade di livello superiore che sarebbe limitato ai gestori di URL diretti e sarebbe in grado di comporre chiamate alla monade DB e alla monade IO.

Si può certamente ottenere questo e ottenere fortissime garanzie statiche sulla separazione dei componenti.

Nel modo più semplice, si desidera una monade IO limitata. Usando qualcosa come una tecnica "tainting", è possibile creare un insieme di operazioni IO innalzate in un semplice wrapper, quindi utilizzare il sistema dei moduli per nascondere i costruttori sottostanti per i tipi.

In questo modo sarà possibile eseguire il codice CGI in un contesto CGI e il codice DB in un contesto DB. Ci sono molti esempi su Hackage.

Un altro modo è costruire un interprete per le azioni e quindi utilizzare i costruttori di dati per descrivere ogni operazione primitiva che si desidera. Le operazioni dovrebbero comunque formare una monade, e puoi usare la notazione, ma dovrai invece costruire una struttura dati che descriva le azioni da eseguire, che poi eseguirai in modo controllato tramite un interprete.

Questo ti dà forse più introspezione di quella che ti serve nei casi tipici, ma l'approccio ti dà pieno potere di capire il codice utente prima di eseguirlo.

+0

Grazie, Don! La prima soluzione suona come quello che sto cercando. Conosci pacchetti specifici che utilizzano questa tecnica, o buoni termini per google ("monade IO limitato" non si è presentato molto)? – Bill

+1

Un buon esempio del concetto "taint monad", http://blog.sigfpe.com/2007/04/trivial-monad.html –

+0

Grazie. Se scelgo di utilizzare il modello "tainted monad" per la mia monade DB, cosa devo fare per estrarre i dati dalla monade DB? Il mio gestore di azioni HTTP deve utilizzare un trasformatore monad con DB in esso? – Bill

4

Grazie per questa domanda!

Ho lavorato su un framework web client/server che utilizzava le monade per distinguere tra diversi ambienti di exection. I più ovvi erano lato client e lato server, ma consentiva anche di scrivere codice fronte-retro (che poteva essere eseguito su client e server, perché non conteneva alcuna funzionalità speciale) e anche lato client asincrono che è stato utilizzato per scrivere codice non bloccante sul client (essenzialmente una monade di continuazione sul lato client). Questo sembra abbastanza legato alla tua idea di distinguere tra codice CGI e codice DB.

Ecco alcune risorse su mio progetto:

  • Slides da una presentazione che ho fatto sul progetto
  • Draft paper che ho scritto con Don Syme
  • E ho anche scritto il mio Bachelor thesis su questo argomento (che è piuttosto lungo però)

Penso che questo sia un approccio interessante e può darvi una garanzia interessante s circa il codice. Ci sono alcune domande complicate però. Se si dispone di una funzione lato server che accetta uno int e restituisce int, quale dovrebbe essere il tipo di questa funzione? Nel mio progetto, ho usato int -> int server (ma può essere anche possibile utilizzare server (int -> int).

Se si dispone di un paio di funzioni di questo tipo, allora non è così semplice per comporre loro. Invece di scrivere goo (foo (bar 1)), è necessario scrivere il codice seguente:.

do b <- bar 1 
    f <- foo b 
    return goo f 

si potrebbe scrivere la stessa cosa utilizzando alcuni combinatori, ma il mio punto è che la composizione è un po 'meno elegante

+0

'import Control.Monad; (goo <= ephemient

+0

Sì, questo era peggio in F # in cui si voleva scrivere anche chiamate di metodo come 'o.Bar() .Poo(). Goo()' e non c'è modo di farlo usando i combinatori. Usare '<= <' in Haskell sembra OK, ma non è ancora perfetto. –

5

Penso che ci sia una terza via al di là dei due Don Stewart menzionato, che può anche essere più semplice:

class Monad m => MonadDB m where 
    someDBop1 :: String -> m() 
    someDBop2 :: String -> m [String] 

class Monad m => MonadCGI m where 
    someCGIop1 :: ... 
    someCGIop2 :: ... 

functionWithOnlyDBEffects :: MonadDB m => Foo -> Bar -> m() 
functionWithOnlyDBEffects = ... 

functionWithDBandCGIEffects :: (MonadDB m, MonadCGI m) => Baz -> Quux -> m() 
functionWithDBandCGIEffects = ... 

instance MonadDB IO where 
    someDBop1 = ... 
    someDBop2 = ... 

instance MonadCGI IO where 
    someCGIop1 = ... 
    someCGIop2 = ... 

L'idea è molto semplice che si definiscono le classi di tipo per i vari sottoinsiemi di operazioni che si desidera separare fuori, e quindi parametrizzare le funzioni che li utilizzano. Anche se l'unica monade concreta che tu abbia mai creato un'istanza delle classi è IO, le funzioni parametrizzate su qualsiasi MonadDB potranno comunque utilizzare solo le operazioni MonadDB (e quelle create da esse), in modo da ottenere il risultato desiderato. E in una funzione "può fare qualsiasi cosa" nella monade IO, puoi usare le operazioni MonadDB e MonadCGI senza problemi, perché IO ​​è un'istanza.

(Naturalmente, è possibile definire altri casi, se si vuole. Ones per sollevare le operazioni attraverso vari trasformatori monade sarebbe semplice, e penso che c'è in realtà nulla ti impedisce di istanze di scrittura per il "wrapper" e Le monadi "interprete" menzionano Don Stewart, combinando così gli approcci, anche se non sono sicuro che ci sia un motivo per cui lo si vorrebbe.)