Niedawno opublikowałem pytanie dotyczące syntactic-2.0 dotyczące definicji share
. Pracowałem w 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
Jednak GHC 7.8 chce -XAllowAmbiguousTypes
się skompilować z tym podpisem. Ewentualnie mogę wymienić fi
z
(ASTF sup (Internal a) -> AST sup ((Internal a) :-> Full (Internal b)) -> ASTF sup (Internal b))
który jest typem sugerowanym przez fundep on SyntacticN
. To pozwala mi uniknąć rozszerzenia. Oczywiście, że tak
- bardzo długi typ, który można dodać do już dużego podpisu
- męczące, aby ręcznie czerpać
- niepotrzebne ze względu na fundep
Moje pytania to:
- Czy jest to dopuszczalne użycie
-XAllowAmbiguousTypes
? - Zasadniczo, kiedy należy użyć tego rozszerzenia? Odpowiedź tutaj sugeruje, że „prawie nigdy nie jest to dobry pomysł”.
Chociaż czytałem dokumenty , nadal mam problem z określeniem, czy ograniczenie jest niejednoznaczne, czy nie. W szczególności rozważ tę funkcję z Data.Syntactic.Sugar:
sugarSym :: (sub :<: AST sup, ApplySym sig fi sup, SyntacticN f fi) => sub sig -> f sugarSym = sugarN . appSym
Wydaje mi się, że
fi
(i być możesup
) powinno być tu dwuznaczne, ale kompiluje się bez rozszerzenia. Dlaczego jestsugarSym
jednoznaczny, pókishare
jest? Ponieważshare
jest to zastosowaniesugarSym
,share
wszystkie ograniczenia pochodzą bezpośredniosugarSym
.
sugarSym Let
, który jest(SyntacticN f (ASTF sup a -> ASTF sup (a -> b) -> ASTF sup b), Let :<: sup) => f
zmiennymi typu niejednoznacznego i nie obejmuje tych zmiennych?share
, ale nie kompilacji, gdy którykolwiek z wymienionych podpisów w pytaniu jest używany. Twoje pytanie zostało również zadane w komentarzach do poprzedniego postuf
tylko jest wystarczające, aby w pełni dwuznacznościsig
,fi
isup
.SyntacticN
marekfi
jednoznacznesugarSym
, ale dlaczego to samo nie odnosi się dofi
wshare
?Odpowiedzi:
Nie widzę żadnej opublikowanej wersji syntactic, której podpis dla
sugarSym
używa tych dokładnych nazw typów, więc będę używał gałęzi programistycznej w commit 8cfd02 ^ , ostatniej wersji, która nadal używała tych nazw.Dlaczego więc GHC narzeka na
fi
podpis w twoim typie, ale nie na tensugarSym
? Dokumentacja, z którą się łączysz, wyjaśnia, że typ jest niejednoznaczny, jeśli nie wydaje się po prawej stronie ograniczenia, chyba że ograniczenie wykorzystuje zależności funkcjonalne do wnioskowania o niejednoznacznym typie z innych niejednoznacznych typów. Porównajmy więc konteksty dwóch funkcji i poszukaj zależności funkcjonalnych.Tak dla
sugarSym
, nie-niejednoznaczne typy sąsub
,sig
if
, i od tych, powinniśmy być w stanie śledzić zależności funkcyjnych w celu disambiguate wszystkie inne typy używanych w kontekście, a mianowiciesup
afi
. I faktycznie,f -> internal
funkcjonalna zależnośćSyntacticN
używa naszegof
do ujednoznacznienia naszegofi
, a następnief -> sig sym
funkcjonalna zależność wApplySym
używa naszego nowego ujednoznacznieniafi
do ujednoznacznieniasup
(isig
, co już było niejednoznaczne). To wyjaśnia, dlaczegosugarSym
nie wymagaAllowAmbiguousTypes
rozszerzenia.Spójrzmy teraz
sugar
. Pierwszą rzeczą, którą zauważam, jest to, że kompilator nie narzeka na dwuznaczny typ, ale raczej na nakładające się wystąpienia:Więc jeśli dobrze to rozumiem, to nie jest tak, że GHC myśli, że twoje typy są niejednoznaczne, ale raczej, że podczas sprawdzania, czy twoje typy są niejednoznaczne, GHC napotkał inny, osobny problem. Mówi ci wtedy, że gdybyś powiedział GHC, aby nie przeprowadzał kontroli niejednoznaczności, nie napotkałby tego osobnego problemu. To wyjaśnia, dlaczego włączenie AllowAmbiguousTypes umożliwia kompilację kodu.
Pozostaje jednak problem z nakładającymi się instancjami. Dwa wystąpienia wymienione przez GHC (
SyntacticN f fi
iSyntacticN (a -> f) ...
) pokrywają się ze sobą. O dziwo, wydaje się, że pierwszy z nich powinien nakładać się na każdy inny podejrzany przypadek. A co to[overlap ok]
znaczyPodejrzewam, że Syntactic jest kompilowany z OverlappingInstances. I patrząc na kod , rzeczywiście tak jest.
Trochę eksperymentując, wydaje się, że GHC jest w porządku z nakładającymi się instancjami, gdy jest jasne, że jedna jest bardziej ogólna niż druga:
Ale GHC nie jest w porządku z nakładającymi się instancjami, gdy żadna z nich nie jest wyraźnie lepsza od drugiej:
Podpis twojego typu używa
SyntacticN (a -> (a -> b) -> b) fi
, i aniSyntacticN f fi
nieSyntacticN (a -> f) (AST sym (Full ia) -> fi)
jest lepiej dopasowany niż drugi. Jeśli zmienię tę część twojego podpisu naSyntacticN a fi
lubSyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi)
, GHC nie będzie już narzekać na nakładanie się.Gdybym był tobą, spojrzałbym na definicję tych dwóch możliwych instancji i ustalił, czy jedna z tych dwóch implementacji jest tą, której chcesz.
źródło
Odkryłem, że
AllowAmbiguousTypes
jest to bardzo wygodne w użyciuTypeApplications
. Rozważ funkcjęnatVal :: forall n proxy . KnownNat n => proxy n -> Integer
z GHC.TypeLits .Aby skorzystać z tej funkcji, mógłbym pisać
natVal (Proxy::Proxy5)
. Alternatywny styl jest do stosowaniaTypeApplications
:natVal @5 Proxy
. TypProxy
jest wywnioskować przez aplikację typu, i to jest denerwujące trzeba pisać za każdym razem, kiedy zadzwonićnatVal
. W ten sposób możemy włączyćAmbiguousTypes
i napisać:Pamiętaj jednak, że gdy przejdziesz do dwuznaczności, nie możesz wrócić !
źródło