C'est assez compliqué. Commençons par l'erreur ambiguë :
<interactive>:1:1:
Ambiguous type variable `b0' in the constraint:
(Arity b0) arising from a use of `arity'
Probable fix: add a type signature that fixes these type variable(s)
In the expression: arity foldr
In an equation for `it': it = arity foldr
Normalement, en l'absence de chevauchement d'instances, lorsque l'on tente de faire correspondre un type à une classe, on compare le type à toutes les instances de cette classe. S'il y a exactement une correspondance, il utilisera cette instance. Dans le cas contraire, vous obtiendrez soit une erreur d'absence d'instance (par exemple avec show (*)
), ou une erreur de chevauchement des instances. Par exemple, si vous supprimez l'élément OverlappingInstances
à partir du programme ci-dessus, vous obtiendrez cette erreur avec arity (&&)
:
<interactive>:1:1:
Overlapping instances for Arity (Bool -> Bool -> Bool)
arising from a use of `arity'
Matching instances:
instance Arity f => Arity (a -> f)
-- Defined at tmp/test.hs:9:10-36
instance Arity x -- Defined at tmp/test.hs:12:10-16
In the expression: arity (&&)
In an equation for `it': it = arity (&&)
Il correspond Arity (a -> f)
comme a
peut être Bool
y f
peut être Bool -> Bool
. Il correspond également Arity x
comme x
peut être Bool -> Bool -> Bool
.
Avec OverlappingInstances
Dans une situation où deux ou plusieurs instances peuvent correspondre, si l'une d'entre elles est la plus spécifique, elle sera choisie. Une instance X
est plus spécifique qu'une instance Y
si X
pourrait correspondre Y
mais pas l'inverse.
Dans ce cas, (a -> f)
correspond à x
mais x
ne correspond pas à (a -> f)
(par exemple, considérez x
être Int
). Ainsi, Arity (a -> f)
est plus spécifique que Arity x
Si les deux correspondent, c'est le premier qui sera choisi.
En utilisant ces règles, arity (&&)
correspondra d'abord à Arity ((->) a f)
avec a
être Bool
y f
être Bool -> Bool
. Le prochain match aura a
être Bool
y f
étant bool. Enfin, il finira par correspondre à Arity x
avec x
étant Bool.
Note avec la fonction ci-dessus, (&&)
le résultat est un type concret Bool
. Mais que se passe-t-il lorsque le type n'est pas concret ? Par exemple, regardons le résultat de arity undefined
. undefined
a le type a
Il ne s'agit donc pas d'un type concret :
<interactive>:1:1:
Ambiguous type variable `f0' in the constraint:
(Arity f0) arising from a use of `arity'
Probable fix: add a type signature that fixes these type variable(s)
In the expression: arity undefined
In an equation for `it': it = arity undefined
Vous obtenez une erreur de variable de type abiguë, tout comme celle pour foldr. Pourquoi cela se produit-il ? C'est parce qu'en fonction de ce que a
est, une instance différente serait nécessaire. Si a
était Int
alors le Arity x
doit correspondre. Si a
était Int -> Int
alors le Arity ((->) a f)
doivent être appariées. A cause de cela, ghc refuse de compiler le programme.
Si vous notez le type de pli : foldr :: forall a b. (a -> b -> b) -> b -> [a] -> b
vous constaterez le même problème : le résultat n'est pas une variable concrète.
C'est ici que IncoherentInstances
entre en jeu : si cette fonction de langage est activée, elle ignorera le problème ci-dessus et choisira simplement une instance qui correspondra toujours à la variable. Par exemple, avec arity undefined
, Arity x
correspondra toujours à a
Le résultat sera donc 0. Une chose similaire est faite à for foldr
.
Maintenant pour le second problème, pourquoi arity $ \x y -> 3
retourner 0 lorsque IncoherentInstaces
est activé ?
C'est un comportement très bizarre. La session ghci suivante montrera à quel point il est bizarre :
*Main> let f a b = 3
*Main> arity f
2
*Main> arity (\a b -> 3)
0
Cela m'amène à penser qu'il y a un bug dans ghc, où \a b -> 3
est vu par IncoherentInstances
pour avoir le type x
au lieu de a -> b -> Int
. Je ne vois pas pourquoi ces deux expressions ne seraient pas exactement les mêmes.