J'ai récemment publié une question sur syntactic-2. concernant la définition de share
. J'ai eu ce travail dans GHC 7.6 :
{-# LANGUAGE GADTs, TypeOperators, FlexibleContexts #-}
import Data.Syntactic
import Data.Syntactic.Sugar.BindingT
data Let a where
Let :: Let (a :-> (a -> b) :-> Full b)
share :: (Let :<: sup,
sup ~ Domain b, sup ~ Domain a,
Syntactic a, Syntactic b,
Syntactic (a -> b),
SyntacticN (a -> (a -> b) -> b)
fi)
=> a -> (a -> b) -> b
share = sugarSym Let
Cependant, GHC 7.8 veut -XAllowAmbiguousTypes
pour compiler avec cette signature. Alternativement, je peux remplacer le fi
par
(ASTF sup (Internal a) -> AST sup ((Internal a) :-> Full (Internal b)) -> ASTF sup (Internal b))
qui est le type impliqué par le fundep sur SyntacticN
. Cela me permet d'éviter l'extension. Bien sûr, c'est
Mes questions sont:
-XAllowAmbiguousTypes
?Bien que j'aie lu la documentation , j'ai toujours du mal à décider si une contrainte est ambiguë ou non. En particulier, considérez cette fonction de Data.Syntactic.Sugar:
sugarSym :: (sub :<: AST sup, ApplySym sig fi sup, SyntacticN f fi)
=> sub sig -> f
sugarSym = sugarN . appSym
Il me semble que fi
(et éventuellement sup
) devrait être ambigu ici, mais il compile sans l'extension. Pourquoi sugarSym
est-il sans ambiguïté alors que share
l'est? Puisque share
est une application de sugarSym
, les contraintes share
viennent toutes directement de sugarSym
.
Je ne vois aucune version publiée de syntaxique dont la signature pour sugarSym
utilise ces noms de type exacts, donc je vais utiliser la branche de développement à commit 8cfd02 ^ , la dernière version qui encore utilisé ces noms.
Alors, pourquoi GHC se plaint-il du fi
dans votre signature de type mais pas celui de sugarSym
? La documentation à laquelle vous avez lié explique qu'un type est ambigu s'il n'apparaît pas à droite de la contrainte, sauf si la contrainte utilise des dépendances fonctionnelles pour déduire le type par ailleurs ambigu à partir d'autres types non ambigus. Comparons donc les contextes des deux fonctions et recherchons les dépendances fonctionnelles.
class ApplySym sig f sym | sig sym -> f, f -> sig sym
class SyntacticN f internal | f -> internal
sugarSym :: ( sub :<: AST sup
, ApplySym sig fi sup
, SyntacticN f fi
)
=> sub sig -> f
share :: ( Let :<: sup
, sup ~ Domain b
, sup ~ Domain a
, Syntactic a
, Syntactic b
, Syntactic (a -> b)
, SyntacticN (a -> (a -> b) -> b) fi
)
=> a -> (a -> b) -> b
Donc pour sugarSym
, les types non ambigus sont sub
, sig
et f
, et parmi ceux-ci nous devrions pouvoir suivre les dépendances fonctionnelles afin de lever l'ambiguïté de tous les autres types utilisés dans le contexte, à savoir sup
et fi
. Et en effet, la dépendance fonctionnelle f -> internal
Dans SyntacticN
utilise notre f
pour lever l'ambiguïté de notre fi
, puis la dépendance fonctionnelle f -> sig sym
Dans ApplySym
utilise notre nouvellement désambiguïsé fi
pour lever l'ambiguïté sup
(et sig
, qui n'était déjà pas ambigu). Cela explique pourquoi sugarSym
ne nécessite pas l'extension AllowAmbiguousTypes
.
Regardons maintenant sugar
. La première chose que je remarque est que le compilateur ne se plaint pas d'un type ambigu, mais plutôt de chevauchements d'instances:
Overlapping instances for SyntacticN b fi
arising from the ambiguity check for ‘share’
Matching givens (or their superclasses):
(SyntacticN (a -> (a -> b) -> b) fi1)
Matching instances:
instance [overlap ok] (Syntactic f, Domain f ~ sym,
fi ~ AST sym (Full (Internal f))) =>
SyntacticN f fi
-- Defined in ‘Data.Syntactic.Sugar’
instance [overlap ok] (Syntactic a, Domain a ~ sym,
ia ~ Internal a, SyntacticN f fi) =>
SyntacticN (a -> f) (AST sym (Full ia) -> fi)
-- Defined in ‘Data.Syntactic.Sugar’
(The choice depends on the instantiation of ‘b, fi’)
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
Donc, si je lis bien, ce n'est pas que GHC pense que vos types sont ambigus, mais plutôt qu'en vérifiant si vos types sont ambigus, GHC a rencontré un problème différent et distinct. Il vous indique ensuite que si vous aviez demandé à GHC de ne pas effectuer la vérification d'ambiguïté, il n'aurait pas rencontré ce problème distinct. Cela explique pourquoi l'activation de AllowAmbiguousTypes permet à votre code de se compiler.
Cependant, le problème avec les instances qui se chevauchent reste. Les deux instances répertoriées par GHC (SyntacticN f fi
Et SyntacticN (a -> f) ...
) se chevauchent. Curieusement, il semble que le premier d'entre eux devrait se chevaucher avec toute autre instance, ce qui est suspect. Et que signifie [overlap ok]
?
Je soupçonne que Syntactic est compilé avec OverlappingInstances. Et en regardant le code , c'est le cas.
En expérimentant un peu, il semble que GHC accepte les instances qui se chevauchent lorsqu'il est clair que l'une est strictement plus générale que l'autre:
{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}
class Foo a where
whichOne :: a -> String
instance Foo a where
whichOne _ = "a"
instance Foo [a] where
whichOne _ = "[a]"
-- |
-- >>> main
-- [a]
main :: IO ()
main = putStrLn $ whichOne (undefined :: [Int])
Mais GHC n'est pas d'accord avec des instances qui se chevauchent lorsque ni l'une ni l'autre n'est clairement mieux adaptée que l'autre:
{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}
class Foo a where
whichOne :: a -> String
instance Foo (f Int) where -- this is the line which changed
whichOne _ = "f Int"
instance Foo [a] where
whichOne _ = "[a]"
-- |
-- >>> main
-- Error: Overlapping instances for Foo [Int]
main :: IO ()
main = putStrLn $ whichOne (undefined :: [Int])
Votre signature de type utilise SyntacticN (a -> (a -> b) -> b) fi
, et ni SyntacticN f fi
Ni SyntacticN (a -> f) (AST sym (Full ia) -> fi)
ne conviennent mieux que les autres. Si je change cette partie de votre signature de type en SyntacticN a fi
Ou SyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi)
, GHC ne se plaint plus du chevauchement.
Si j'étais vous, je regarderais la définition de ces deux instances possibles et déterminer si l'une de ces deux implémentations est celle que vous voulez.
J'ai découvert que AllowAmbiguousTypes
est très pratique à utiliser avec TypeApplications
. Considérez la fonction natVal :: forall n proxy . KnownNat n => proxy n -> Integer
De GHC.TypeLits .
Pour utiliser cette fonction, j'ai pu écrire natVal (Proxy::Proxy5)
. Un autre style consiste à utiliser TypeApplications
: natVal @5 Proxy
. Le type de Proxy
est déduit par l'application de type, et c'est ennuyeux de devoir l'écrire chaque fois que vous appelez natVal
. Ainsi, nous pouvons activer AmbiguousTypes
et écrire:
{-# Language AllowAmbiguousTypes, ScopedTypeVariables, TypeApplications #-}
ambiguousNatVal :: forall n . (KnownNat n) => Integer
ambiguousNatVal = natVal @n Proxy
five = ambiguousNatVal @5 -- no `Proxy ` needed!
Cependant, notez qu'une fois que vous devenez ambigu, vous ne pouvez pas revenir en arrière !