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
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? –
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
@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