2

Je suis actuellement en train de construire un serveur en haskell et en tant que débutant à la langue, je voudrais essayer une nouvelle approche de la composition Monad. L'idée est que nous pouvons écrire des méthodes de bibliothèque telles queComment faire abstraction de monades sans combattre le système de types dans Haskell?

isGetRequest :: (SupportsRequests m r) => m Bool 
    isGetRequest = do 
     method <- liftRequests $ requestMethod 
     return $ method == GET 

    class (Monad m, RequestSupport r) => SupportsRequests m r | m -> r where 
     liftRequests :: r a -> m a 

    class (Monad r) => RequestSupport r where 
     requestMethod :: r Method 

qui fonctionnent sans connaître la monade sous-jacente. Bien sûr, dans cet exemple, il aurait suffi de faire fonctionner isGetRequest directement sur la monade (RequestSupport r) mais l'idée est que ma bibliothèque pourrait aussi avoir plus d'une contrainte sur la monade. Pourtant, je ne veux pas mettre en œuvre toutes ces différentes préoccupations dans le même module ni les répartir entre différents modules (instances orphelines!). C'est pourquoi la monade m implémente seulement les classes Supports*, en déléguant les vraies préoccupations à d'autres monades.

Le code ci-dessus devrait fonctionner parfaitement (avec quelques extensions de langage pour GHC). Malheureusement, je suis arrivé quelques problèmes avec la CRUD (Créer Lire Update Supprimer) concernent:

class (Monad m, CRUDSupport c a) => SupportsCRUD m c a | m a -> c where 
    liftCRUD :: c x -> m x 

class (Monad c) => CRUDSupport c a | c -> a where 
    list :: c [a] -- List all entities of type a 

Non, j'obtiens une erreur:

Could not deduce (SupportsCRUD m c a0) from the context [...] 
The type variable 'a0' is ambiguous [...] 
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes 
When checking the class method: liftCRUD [...] 

On dirait que le type vérificateur n'aime pas que le paramètre a ne se pose pas directement dans la signature de liftCRUD. C'est compréhensible car a ne peut pas être dérivé des dépendances fonctionnelles. Le vérificateur de type dans mon cerveau me dit qu'il ne devrait pas être un problème d'inférer le type a plus tard, en utilisant AllowAmbiguousTypes, quand une méthode concernant CRUD est exécutée dans une méthode de bibliothèque. Malheureusement, GHC semble incapable de faire cette étape d'inférence, par exemple

bookAvailable :: (SupportsCRUD m c Book) => m Bool 
bookAvailable = do 
    books <- liftCRUD (list :: c [Book]) -- I use ScopedTypeVariables 
    case books of 
     [] -> return False 
     _ -> return True 

cède

Could not deduce (SupportsCRUD m c0 a1) arising from a use of 'liftCRUD' [...] 
The type variables c0, a1 are ambiguous [...] 

Il semble que je suis toujours incapable de raisonner sur le compilateur. Y at-il un moyen de résoudre ce problème? Ou au moins une façon de comprendre ce que le compilateur peut déduire?

Cordialement, Bloxx

+3

La déclaration de classe 'SupportsCRUD' implique trois variables de type,' m', 'c' et' a'. Pouvez-vous dire quelles valeurs vous attendez du compilateur pour déduire ces trois variables dans votre exemple, et pourquoi? –

+1

Qu'avez-vous prévu par votre fundep 'm a -> c'? Cela signifie que "le choix de' c' est uniquement déterminé par les types spécifiques 'm' et' a' ", ce qui a pour effet que le vérificateur de type peut choisir une instance basée uniquement sur' m' et 'a' dans contexte, et le 'c' découle de tout ce que vous avez mis dans la tête de l'instance. – jberryman

+0

@jberryman Il devrait en fait dire au compilateur que pour le monad m et l '"entité CRUD" a, le vérificateur de type peut choisir une monade unique c (c'est celle qui supporte les opérations CRUD). – bloxx

Répondre

3

Pour utiliser ScopedTypeVariables vous devez également lier les variables que vous voulez être portée avec forall. Il doit donc être

bookAvailable :: forall m c. (SupportsCRUD m c Book) => m Bool 
... 

C'était tout ce qui était nécessaire (après quelques corrections triviales j'ai fait que je suppose que les fautes de frappe étaient d'entrer dans votre question) pour moi d'obtenir le code pour compiler.

+0

Wow! Eh bien, cela me surprend tellement que maintenant j'ai une question: pourquoi GHC a-t-il voulu choisir «a ~ Book» en déduisant le type de «liftCRUD»? Clairement avec ce choix il y a une instance appropriée disponible; mais tout aussi clairement on pourrait venir plus tard et ajouter un autre exemple qui serait aussi un bon choix, qui me crie "ambigu". –

+2

Aha, et après avoir essayé de faire un témoignage pour mon affirmation que "clairement on pourrait venir plus tard et ajouter une autre instance", je pense peut-être que je vois la raison: il y a une clé 'c-> a' fundep sur' CRUDSupport' , et toutes les instances 'SupportsCRUD' impliquent une instance' CRUDSupport' correspondante. –

+0

@DanielWagner ouais ça m'a fait trébucher moi aussi. J'ai manqué que 'CRUDSupport' était une superclasse de' SupportsCRUD' parce qu'elle a été déclarée après, et je suis habitué aux superclasses arrivant en premier. – luqui