Não vejo nenhuma versão publicada do sintático cuja assinatura sugarSym
use exatamente esses nomes de tipos, portanto, usarei o ramo de desenvolvimento em commit 8cfd02 ^ , a última versão que ainda usava esses nomes.
Então, por que o GHC se queixa da fi
assinatura do seu tipo, mas não a assinatura sugarSym
? A documentação à qual você vinculou explica que um tipo é ambíguo se não aparecer à direita da restrição, a menos que ela esteja usando dependências funcionais para inferir o tipo ambíguo de outros tipos não ambíguos. Então, vamos comparar os contextos das duas funções e procurar dependências funcionais.
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
Portanto sugarSym
, os tipos não ambíguos são sub
, sig
e f
, dentre esses, devemos ser capazes de seguir dependências funcionais para desambiguar todos os outros tipos usados no contexto, a saber, sup
e fi
. E, de fato, a f -> internal
dependência funcional SyntacticN
usa nosso f
para desambiguar nossa fi
e, posteriormente, a f -> sig sym
dependência funcional ApplySym
usa nosso recém-desambiguar fi
para desambiguar sup
(e sig
, que já era não ambíguo). Então, isso explica por sugarSym
que não requer a AllowAmbiguousTypes
extensão.
Vamos agora olhar sugar
. A primeira coisa que noto é que o compilador não está reclamando de um tipo ambíguo, mas sim de instâncias sobrepostas:
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
Portanto, se estou lendo isso corretamente, não é que o GHC pense que seus tipos são ambíguos, mas sim, ao verificar se seus tipos são ambíguos, o GHC encontrou um problema diferente e separado. É então dizer que se você dissesse ao GHC para não executar a verificação de ambiguidade, ele não teria encontrado esse problema separado. Isso explica por que ativar o AllowAmbiguousTypes permite que seu código seja compilado.
No entanto, o problema com as instâncias sobrepostas permanece. As duas instâncias listadas pelo GHC ( SyntacticN f fi
e SyntacticN (a -> f) ...
) se sobrepõem. Estranhamente, parece que o primeiro deles deve se sobrepor a qualquer outra instância, o que é suspeito. E o que isso [overlap ok]
significa?
Suspeito que o Syntactic seja compilado com OverlappingInstances. E olhando o código , de fato ele faz.
Experimentando um pouco, parece que o GHC concorda com instâncias sobrepostas quando fica claro que uma é estritamente mais geral que a outra:
{-# 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])
Mas o GHC não concorda com instâncias sobrepostas quando nenhuma delas é claramente mais adequada que a outra:
{-# 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])
Sua assinatura de tipo usa SyntacticN (a -> (a -> b) -> b) fi
, e SyntacticN f fi
nem SyntacticN (a -> f) (AST sym (Full ia) -> fi)
é melhor que a outra. Se eu alterar essa parte da sua assinatura de tipo para SyntacticN a fi
ou SyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi)
, o GHC não reclamará mais da sobreposição.
Se eu fosse você, examinaria a definição dessas duas instâncias possíveis e determinaria se uma dessas duas implementações é a que você deseja.
sugarSym Let
, que é(SyntacticN f (ASTF sup a -> ASTF sup (a -> b) -> ASTF sup b), Let :<: sup) => f
e não envolve variáveis de tipo ambíguas?