35 votes

Comment fonctionne IncoherentInstances ?

Jouer avec un peu de code :

{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}

class Arity f where
  arity :: f -> Int

instance Arity x where
  arity _ = 0

instance Arity f => Arity ((->) a f) where
  arity f = 1 + arity (f undefined)

Sans IncoherentInstances :

ghci> arity foldr
blah blah ambiguous blah blah possible fix blah
ghci> arity (foldr :: (a -> Int -> Int) -> Int -> [a] -> Int)
3
ghci> let f x y = 3 in arity f
2
ghci> arity $ \x y -> 3
2

Si nous ajoutons IncoherentInstances à la liste des pragmas, alors il peut traiter foldr sans avoir besoin d'une signature de type monomorphique, mais il obtient une mauvaise réponse pour les lambdas :

ghci> arity foldr
3
ghci> let f x y = 3 in arity f
2
ghci> arity $ \x y -> 3 -- should be 2
0

Quelle est la magie noire derrière les Instances Incohérentes ? Pourquoi fait-elle ce qu'elle fait ici ?

30voto

David Miani Points 10548

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.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X