0
votes

Se chevauchant des instances multi-paramètres et la spécificité d'instance

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
module OverlappingSpecificsError where

class EqM a b where
    (===) :: a -> b -> Bool

instance {-# OVERLAPPABLE #-} Eq a => EqM a a where
    a === b = a == b

instance {-# OVERLAPPABLE #-} EqM a b where
    a === b = False

aretheyreallyeq :: (Eq a, Eq b) => Either a b -> Either a b -> Bool
aretheyreallyeq (Left a1) (Right b2) = a1 == b2

aretheyeq :: (Eq a, Eq b) => Either a b -> Either a b -> Bool
aretheyeq (Left a1) (Right b2) = a1 === b2
Neither aretheyreallyeq or aretheyeq compile, but the error for aretheyreallyeq makes sense to me, and also tells me that aretheyeq should not give an error: One of the instances that GHCi suggests are possible for EqM in aretheyeq should be impossible due to the same error on aretheyreallyeq. What's going on?The point is, GHCi insists that both of the instances of EqM are applicable in aretheyeq. But a1 is of type a and b2 is of type b, so in order for the first instance to be applicable, it would have to have the types a and b unify.But this should not be possible, since they are declared as type variables at the function signature (that is, using the first EqM instance would give rise to the function being of type Either a a -> Either a a -> Bool, and the error in aretheyreallyeq tells me that GHCi will not allow that (which is what I expected anyway).Am I missing something, or is this a bug in how overlapping instances with multi-parameter type classes are checked?I am thinking maybe it has to do with the fact that a and b could be further instantiated later on to the point where they are equal, outside aretheyeq, and then the first instance would be valid? But the same is true for aretheyreallyeq. The only difference is that if they do not ever unify we have an option for aretheyeq, but we do not for aretheyreallyeq. In any case, Haskell does not have dynamic dispatch for plenty of good and obvious reasons, so what is the fear in committing to the instance that will always work regardless of whether later on a and b are unifiable? Maybe there is some way to present this that would make choosing the instance when calling the function possible in some way?It is worth noting that if I remove the second instance, then the function obviously still does not compile, stating that no instance EqM a b can be found. So if I do not have that instance, then none works, but when that one works, suddenly the other does too and I have an overlap? Smells like bug to me miles away.

1 commentaires

Vous voudrez peut-être comparer les types A et B pour égalité à runtime au lieu de compiler heure. Si tel est le cas, vous pouvez utiliser typéable A, typable B comme une contrainte supplémentaire. Notez que si vous le faites, vous devrez satisfaire les contraintes à chaque appel.


4 Réponses :


-1
votes

artheyeallyeq échoue car il existe deux variables de type différentes dans la portée. Dans xxx

a1 :: a , et b2 :: b , il n'y a pas de méthode de comparaison des valeurs de types potentiellement différents (comme ceci est la façon dont ils sont déclarés), donc cela échoue. Cela n'a rien à voir avec aucune des extensions ou pragmes activées bien sûr.

Aretheyeq échoue car il y a deux instances que pourrait match, pas que ils font définitivement. Je ne sais pas quelle version de GHC que vous utilisez, mais voici le message d'exception que je vois et il semble être assez clair pour moi: xxx

Dans ce cas, mon L'interprétation est que cela dit que cela donne certains choix pour A et B, il existe potentiellement plusieurs instances de correspondance différentes.


1 commentaires

Je sais déjà tout ça. Mais je pense que vous avez échoué à comprendre mon point. La deuxième instance est EQM A A A . Pour que cela correspond à cela compte tenu de la signature de arrétheyeallyeq , il devrait forcer arrétheyeallyeq pour avoir des variables de type a et b < / code> être égal. Si vous avez pu faire cela, alors arrétheyeq serait également de type chèque. Ce qui est prouvé, au fait, si vous supprimez simplement l'autre instance de EQM : cette fonction ne compile toujours pas, car l'instance qu'il affirme se chevaucherait n'est pas une instance valide. L'instance EQM A A A ne pourrait jamais correspondre.



3
votes

La correspondance d'instance sur les variables génériques fonctionne de cette façon afin d'empêcher certains scénarios potentiellement déroutants (et dangereux).

Si le compilateur a donné à votre intuition et a choisi l'instance EQM AB CODE> lors de la compilation arrétheyeq code> (car A code> et b code> ne l'unifiez pas nécessairement, comme vous le dites), alors l'appel suivant: p>

dummy :: a -> b -> Bool
dummy a b = aretheyeq (Left a) (Right b)


5 commentaires

Je pense que vous n'avez pas non plus compris mon point. Je ne veux vraiment pas que l'instance eqm a une instance soit utilisée dans arrétheyeq . Jamais. Donc, vous faites fondamentalement mon point pour moi: il n'y a pas de dépêche dynamique, sans deviner. Il devrait simplement choisir l'instance EQM A B et être effectuée avec elle. Pourquoi est-ce même un problème que l'instance EQM A A , qui ne fonctionnerait jamais, existe?


Si j'avais la contrainte EQM A B sur la fonction, ce serait une histoire complète. Ensuite, la fonction doit compiler, aucune question posée, puis peut-être peut-être parfois utiliser l'instance EQM A A lorsqu'elle est appelée avec des types plus instanciés qui rendent celui-ci possible. Et dans un tel cas, le chevauchement serait vérifié de toute façon au niveau supérieur où il a été appelé. Vérifiez donc cela ici, où je n'ai pas cette contrainte sur la fonction, ne sert à rien d'autre que de rendre ma vie difficile.


"Je pense que vous n'avez pas non plus compris mon point" - je répondais en fait que dans le tout premier paragraphe: si le compilateur a toujours choisi EQM A B dans ces circonstances, cela conduirait à des résultats inattendus comme ça.


J'ai oublié cette première partie après avoir lu le reste. Je ne peux pas comprendre pourquoi cela serait contraire à l'intuition. L'utilisation de === est à l'intérieur de la fonction. Le type de fonction n'a pas de contrainte EQM . Ainsi, lorsque vous l'utilisez, vous n'avez même pas besoin de savoir qu'il utilise EQM . Lors de la programmation, vous savez que A et B sont des variables de type différentes et que l'instance EQM A A ne serait jamais utilisée. Quelle confusion est là pour avoir eu? Si vous avez essayé de deviner à quel point chaque fonction utilisée est mise en œuvre, vous seriez condamné. La fonction devrait décrire ce qu'elle fait.


Il y a une différence entre une explication formelle et une attente intuitive. L'espoir que l'instance "droite" soit choisie par magie est assez courante que les concepteurs de compilateur choisissent de rejeter cette situation au prix de la rejet des programmes corrects. C'est le choix fondamental de la conception de la langue. Chaque fonctionnalité fait ce compromis.



2
votes

Ce n'est pas un bug en sens de travail exactement comme documenté . Commençant par

Supposons maintenant que, dans un module client, nous recherchons une instance de la contrainte cible (c Ty1 .. TYN) . La recherche fonctionne comme ceci:

La première étape de la recherche instances candidates fonctionne comme prévu; EQM A B est le seul candidat et donc le candidat principal. Mais la dernière étape est

Déterminez maintenant toutes les instances ou des contraintes données intégrées, qui unifient avec la contrainte cible, mais ne le correspondent pas. Ces instances non candidates peuvent correspondre lorsque la contrainte cible est ultérieure instanciée. Si tous sont des instances de haut niveau incohérentes, la recherche réussit, renvoyant le premier candidat. Sinon, la recherche échoue.

Le EQM A A A L'instance tombe dans cette catégorie et n'est pas incohérente, la recherche échoue. Et vous pouvez obtenir le comportement que vous voulez en le marquant comme {- # incoherent # -} au lieu de superposable.


2 commentaires

J'avais lu cette page un million de fois avant de poster cela, mais je n'avais pas compris que le dernier bit a été référé précisément à cela. Merci d'avoir fait remarquer cela. Jamais tout à fait compris ce que cela signifiait par «cela unifie, mais cela ne correspond pas». Je ne pense pas que le marquant aussi incohérent fonctionnera pour ma demande réelle, comme dans ce cas, j'ai deux cas qui sont en quelque sorte symétrique, mais chacun d'entre eux entrez sur l'autre sur différentes situations, en trouvant une instanciation potentielle non assortie comme dans le cas j'ai présenté. Je devrais les marquer à la fois aussi incohérents et cela ne fonctionnerait probablement pas?


Cela finira probablement de «si tous les candidats restants sont incohérents, la recherche réussit, renvoyant un candidat survivant arbitraire» dans au moins certaines situations. Que ce soit acceptable pour votre cas d'utilisation, je ne sais pas.



0
votes

Pour compléter davantage la réponse d'Alexey, qui m'a vraiment donné l'indicateur sur ce que je devrais faire pour atteindre le comportement que je voulais, j'ai compilé l'exemple minimum de travail suivant d'une situation légèrement différente de mon cas de réel usage (ce qui doit faire avec existentiensquantifantification ): xxx

si vous supprimez le {- # incoherent # -} annotations, vous obtenez exactement les mêmes erreurs de compilation que dans mon exemple original sur Createbar et createbar2 , et le point est identique: dans Createbar Il est clair que la seule instance appropriée est la seule instance appropriée. Deuxièmement, alors que dans CreateBar2 Le seul approprié est le seul est le premier, mais Haskell refuse de compiler en raison de cette confusion apparente qu'il pourrait créer lors de l'utilisation, jusqu'à ce que vous les annotant avec incoherent .

et ensuite, le code fonctionne exactement comme vous l'attendez: getta Createbar retourne gauche "ABC" < / code> alors que g eTA CreateBar2 renvoie Droite "DEFDEF" , qui est exactement la seule chose qui pourrait arriver dans un système de type sensible.

Donc, ma conclusion est la suivante: le IncoHerent ANNOTATION est précisément pour permettre ce que je voulais faire depuis le début sans haskell se plaignant des instances potentiellement déroutantes et de prendre la seule personne qui a du sens. Un doute reste quant à savoir si incohérent peut le faire afin que des instances qui restent en effet se chevauchent, même après avoir pris en compte tout ce qui compilait, en utilisant un arbitraire (qui est évidemment mauvais et dangereux). Donc, un corollaire à ma conclusion est: Utilisez uniquement incohérent lorsque vous avez absolument besoin de et êtes absolument convaincu qu'il n'y a en effet qu'une seule instance valide.

Je pense toujours que c'est un Bit Absurd que Haskell n'a pas de moyen plus naturel et sans danger de dire au compilateur d'arrêter de m'inquiéter de moi étant potentiellement confus et de faire ce qui est évidemment la seule vérification de type au problème ...


0 commentaires