2010-03-05 5 views
15

Je pense à des façons d'utiliser le système de type Haskell pour renforcer la modularité d'un programme. Par exemple, si j'ai une application Web, je suis curieux de savoir s'il existe un moyen de séparer tout le code de la base de données du code CGI du code du système de fichiers du code pur.Utilisation du système de type de Haskell pour renforcer la modularité

Par exemple, je suis envisager une monade DB, donc je pourrais écrire des fonctions comme:

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

Je voudrais qu'il soit impossible d'utiliser des effets secondaires autres que ceux pris en charge par la DB monade. J'imagine une monade de niveau supérieur qui serait limitée aux gestionnaires d'URL directs et serait capable de composer des appels à la monade DB et la monade IO.

Est-ce possible? Est-ce sage?

Mise à jour: J'ai fini par y parvenir avec Scala au lieu de Haskell: http://moreindirection.blogspot.com/2011/08/implicit-environment-pattern.html

Répondre

13

J'imaginais une monade de niveau supérieur qui serait limitée aux gestionnaires d'URL directs et serait en mesure de composer des appels à la monade DB et la monade IO.

Vous pouvez certainement y parvenir, et obtenir des garanties statiques très fortes sur la séparation des composants. Dans sa version la plus simple, vous voulez une monade IO restreinte. En utilisant quelque chose comme une technique "d'altération", vous pouvez créer un ensemble d'opérations d'E/S soulevées dans un encapsuleur simple, puis utiliser le système de module pour cacher les constructeurs sous-jacents pour les types. De cette manière, vous ne pourrez exécuter que du code CGI dans un contexte CGI et du code DB dans un contexte de base de données. Il y a beaucoup d'exemples sur Hackage.

Une autre façon est de construire un interpréteur pour les actions, puis d'utiliser des constructeurs de données pour décrire chaque opération primitive que vous souhaitez. Les opérations doivent toujours former une monade, et vous pouvez utiliser la notation Do, mais vous construisez plutôt une structure de données qui décrit les actions à exécuter, que vous exécutez ensuite de manière contrôlée via un interpréteur. Cela vous donne peut-être plus d'introspection que ce dont vous avez besoin dans des cas typiques, mais l'approche vous donne toute la puissance nécessaire pour inspecter le code utilisateur avant de l'exécuter.

+0

Merci, Don! L'ancienne solution ressemble à ce que je cherche. Connaissez-vous des paquets spécifiques qui utilisent cette technique, ou de bons termes à google pour ("Mono IO restreint" n'a pas beaucoup augmenté)? – Bill

+1

Un bon exemple du concept 'tay monad', http://blog.sigfpe.com/2007/04/trivial-monad.html –

+0

Merci. Si je choisis d'utiliser le modèle "monad contaminé" pour mon monad DB, que puis-je faire pour extraire les données de la monade DB? Mon gestionnaire d'action HTTP doit-il utiliser un transformateur monad avec DB? – Bill

4

Merci pour cette question!

J'ai travaillé sur un framework web client/serveur qui utilisait des monads pour faire la distinction entre différents environnements d'exection. Les plus évidentes étaient côté client etcôté serveur, mais il vous a également permis d'écrire à la fois du code côté (qui pourrait fonctionner à la fois client et serveur, car il ne contenait aucune des caractéristiques particulières) et aussi asynchrone côté client qui a été utilisé pour écrire du code non-bloquant sur le client (essentiellement une monade de continuation sur le côté client). Cela semble assez lié à votre idée de distinguer entre le code CGI et le code DB.

Voici quelques ressources sur mon projet:

  • Slides d'une présentation que je l'ai fait au sujet du projet
  • Draft paper que j'ai écrit avec Don Syme
  • Et j'ai aussi écrit mon Bachelor thesis sur ce sujet (qui est assez long si)

Je pense que c'est une approche intéressante et il peut vous donner une garantie intéressante s à propos du code. Il y a quelques questions difficiles cependant. Si vous avez une fonction côté serveur qui prend un int et renvoie int, alors quel devrait être le type de cette fonction? Dans mon projet, je int -> int server (mais il peut être également possible d'utiliser server (int -> int).

Si vous avez quelques fonctions comme celle-ci, alors il est pas aussi simple de les composer. Au lieu d'écrire goo (foo (bar 1)), vous avez besoin d'écrire le code suivant:.

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

Vous pouvez écrire la même chose en utilisant des combinateurs, mais mon point est que la composition est un peu moins élégante

+0

'import Control.Monad; (goo <= ephemient

+0

Ouais, c'était pire dans F # où vous vouliez aussi écrire des appels de méthodes tels que 'o.Bar(). Foo(). Goo()' et il n'y a aucun moyen de le faire en utilisant des combinateurs. Utiliser '<= <' dans Haskell semble OK, mais toujours pas parfait. –

5

Je pense qu'il ya une troisième voie au-delà des deux Don Stewart mentionné, qui peut même être plus simple:

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'idée est tout simplement que vous définissez les classes de type pour les différents sous-ensembles d'opérations que vous souhaitez séparer out, puis paramétrer vos fonctions en les utilisant. Même si la seule monade concrète que vous ayez jamais créée est une IO, les fonctions paramétrées sur n'importe quel MonadDB seront toujours autorisées à utiliser les opérations MonadDB (et celles qui en sont construites), afin d'obtenir le résultat souhaité. Et dans une fonction "peut faire n'importe quoi" dans la monade IO, vous pouvez utiliser les opérations MonadDB et MonadCGI de façon transparente, car IO est une instance.

(Bien sûr, vous pouvez définir d'autres cas, si vous voulez. Ones pour soulever les opérations par le biais de divers transformateurs monade serait simple, et je pense qu'il est en fait rien ne vous empêche des instances d'écriture pour le « emballage » et les monades "interprètes" Don Stewart mentionne, combinant ainsi les approches - bien que je ne sois pas sûr s'il y a une raison que vous voudriez.)

Questions connexes