Non vedo alcuna versione pubblicata di sintattica la cui firma per sugarSym
utilizza quei nomi esatti di tipi, quindi userò il ramo di sviluppo su commit 8cfd02 ^ , l'ultima versione che ancora utilizzava quei nomi.
Quindi, perché GHC si lamenta della fi
firma del tuo tipo ma non di quella sugarSym
? La documentazione a cui hai collegato spiega che un tipo è ambiguo se non appare a destra del vincolo, a meno che il vincolo non stia usando dipendenze funzionali per inferire il tipo altrimenti ambiguo da altri tipi non ambigui. Quindi confrontiamo i contesti delle due funzioni e cerchiamo dipendenze funzionali.
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
Quindi sugarSym
, i tipi non ambigui sono sub
, sig
e f
, e da quelli che dovremmo essere in grado di seguire le dipendenze funzionali al fine di chiarire tutti gli altri tipi utilizzati nel contesto, vale a dire sup
e fi
. E in effetti, la f -> internal
dipendenza funzionale in SyntacticN
usa il nostro f
per chiarire le nostre fi
, e in seguito la f -> sig sym
dipendenza funzionale in ApplySym
usa il nostro appena chiarito fi
per chiarire sup
(e sig
, che era già non ambiguo). Questo spiega perché sugarSym
non richiede l' AllowAmbiguousTypes
estensione.
Vediamo ora sugar
. La prima cosa che noto è che il compilatore non si lamenta di un tipo ambiguo, ma piuttosto di istanze sovrapposte:
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
Quindi, se sto leggendo bene, non è che GHC pensi che i tuoi tipi siano ambigui, ma piuttosto, mentre controlla se i tuoi tipi sono ambigui, GHC ha riscontrato un problema diverso e separato. Ti sta quindi dicendo che se avessi detto a GHC di non eseguire il controllo dell'ambiguità, non avrebbe riscontrato quel problema separato. Questo spiega perché abilitando AllowAmbiguousTypes è possibile compilare il codice.
Tuttavia, il problema con le istanze sovrapposte rimane. Le due istanze elencate da GHC ( SyntacticN f fi
e SyntacticN (a -> f) ...
) si sovrappongono. Stranamente, sembra che il primo di questi dovrebbe sovrapporsi a qualsiasi altra istanza, il che è sospetto. E cosa [overlap ok]
significa?
Sospetto che Syntactic sia compilato con OverlappingInstances. E guardando il codice , in effetti lo fa.
Sperimentando un po ', sembra che GHC stia bene con casi sovrapposti quando è chiaro che uno è strettamente più generale dell'altro:
{-# 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])
Ma GHC non va bene con casi sovrapposti in cui nessuno dei due è chiaramente migliore degli altri:
{-# 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])
La tua firma di tipo utilizza SyntacticN (a -> (a -> b) -> b) fi
e né SyntacticN f fi
né SyntacticN (a -> f) (AST sym (Full ia) -> fi)
si adatta meglio dell'altra. Se cambio quella parte della firma del tuo tipo in SyntacticN a fi
o SyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi)
, GHC non si lamenta più della sovrapposizione.
Se fossi in te, esaminerei la definizione di quei due possibili casi e determinerei se una di quelle due implementazioni è quella che desideri.
sugarSym Let
, che è(SyntacticN f (ASTF sup a -> ASTF sup (a -> b) -> ASTF sup b), Let :<: sup) => f
e non coinvolge variabili di tipo ambigue?