No veo ninguna versión publicada de Syntactic cuya firma sugarSym
use esos nombres de tipo exactos, por lo que usaré la rama de desarrollo en commit 8cfd02 ^ , la última versión que todavía usa esos nombres.
Entonces, ¿por qué GHC se queja de la fi
firma de su tipo pero no de la firma sugarSym
? La documentación a la que se ha vinculado explica que un tipo es ambiguo si no aparece a la derecha de la restricción, a menos que la restricción esté utilizando dependencias funcionales para inferir el tipo ambiguo de otros tipos no ambiguos. Entonces, comparemos los contextos de las dos funciones y busquemos dependencias funcionales.
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
Entonces sugarSym
, los tipos no ambiguos son sub
, sig
y f
, y de ellos deberíamos poder seguir las dependencias funcionales para desambiguar todos los otros tipos utilizados en el contexto, a saber, sup
y fi
. Y, de hecho, la f -> internal
dependencia funcional en SyntacticN
utiliza nuestro f
para desambiguar nuestro fi
, y luego la f -> sig sym
dependencia funcional en ApplySym
usa nuestro recién desambiguado fi
para desambiguar sup
(y sig
, que ya no era ambiguo). Eso explica por qué sugarSym
no requiere la AllowAmbiguousTypes
extensión.
Veamos ahora sugar
. Lo primero que noto es que el compilador no se queja de un tipo ambiguo, sino de instancias superpuestas:
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
Entonces, si estoy leyendo esto correctamente, no es que GHC piense que sus tipos son ambiguos, sino que mientras verifica si sus tipos son ambiguos, GHC encontró un problema diferente y diferente. Luego le dice que si le dijera a GHC que no realizara la verificación de ambigüedad, no habría encontrado ese problema por separado. Esto explica por qué habilitar AllowAmbiguousTypes permite que su código se compile.
Sin embargo, el problema con las instancias superpuestas permanece. Las dos instancias enumeradas por GHC ( SyntacticN f fi
y SyntacticN (a -> f) ...
) se superponen entre sí. Por extraño que parezca, parece que el primero de estos debería superponerse con cualquier otra instancia, lo que es sospechoso. Y que [overlap ok]
significa
Sospecho que Syntactic está compilado con OverlappingInstances. Y mirando el código , de hecho lo hace.
Experimentando un poco, parece que GHC está de acuerdo con instancias superpuestas cuando está claro que una es estrictamente más general que la otra:
{-# 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])
Pero GHC no está de acuerdo con las instancias superpuestas cuando ninguna de ellas es claramente mejor que la otra:
{-# 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])
Sus usos tipo de firma SyntacticN (a -> (a -> b) -> b) fi
, y ni SyntacticN f fi
tampoco SyntacticN (a -> f) (AST sym (Full ia) -> fi)
es un ajuste mejor que el otro. Si cambio esa parte de su firma tipo a SyntacticN a fi
o SyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi)
, GHC ya no se queja de la superposición.
Si yo fuera usted, miraría la definición de esas dos posibles instancias y determinaría si una de esas dos implementaciones es la que desea.
sugarSym Let
, que es(SyntacticN f (ASTF sup a -> ASTF sup (a -> b) -> ASTF sup b), Let :<: sup) => f
y no involucra variables de tipo ambiguas?