a => X a
class Eq c => Y c
instance X a => Y a
Где то написано, почему такое не тайпчекается, то есть что надо instance (Eq a, X a) => Y a писать? Как то нелогично что ограничения на класс X игнорируются
Это побочный эффект UndecidableInstances: поскольку мы отложили вычисление цепочки инстансов до рантайма, то и бонусов от него не получим. Если убрать необходимость в UndecidableInstances, то снова работает, например, module Smth where class Eq a => X a class Eq c => Y c instance X a => Y [a]
Эм. До рантайма? Казалось бы, если например циклится - сразу говорит
Но так то пример интересный, спасибо
Как это говорит? В том-то и фишка UndecidableInstances, что компилятор не будет проверять тотальность и позволит писать штуки вроде {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE UndecidableInstances #-} module Smth where class Eq a => X a class Eq c => Y c instance (Eq a, X a) => Y a instance (Eq a, Y a) => X a
В функциях говорит
$p1X :: forall a. X a => Eq a $p1X = \(@a) ($x :: X a) -> case $x of $x { X eq _ -> eq } x :: forall a. X a => a x = \(@a) ($x :: X a) -> case $x of $x { X _ x -> x } $cp1Y_r1R2 :: forall a. X a => Eq [a] $cp1Y_r1R2 = \(@a) ($x :: X a) -> GHC.Classes.$fEq[] @a ($p1X @a $x) $cy_r1Q8 :: forall a. X a => [a] $cy_r1Q8 = \(@a) ($x :: X a) -> : @a (x @a $x) ([] @a) $fY[] :: forall a. X a => Y [a] $fY[] = \(@a) ($x :: X a) -> Y @[a] ($cp1Y_r1R2 @a $x) ($cy_r1Q8 @a $x) действительно, без undecidable он вытаскивает eq из x
А, не говорит. Извиняюсь. Правда несуществующие инстансы и циклящиеся функции
Главное, что добавление прагмы не ломает подобный (работающий без неё) код, в общем. На производительности кстати интересно как сказывается
Если верить нотам гхц (и если я их правильно понимаю) это все-таки так сделано чтобы избежать боттомов в суперклассах. Note [Recursive superclasses] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ See #3731, #4809, #5751, #5913, #6117, #6161, which all describe somewhat more complicated situations, but ones encountered in practice. See also tests tcrun020, tcrun021, tcrun033, and #11427. ----- THE PROBLEM -------- The problem is that it is all too easy to create a class whose superclass is bottom when it should not be. Consider the following (extreme) situation: class C a => D a where ... instance D [a] => D [a] where ... (dfunD) instance C [a] => C [a] where ... (dfunC) Although this looks wrong (assume D [a] to prove D [a]), it is only a more extreme case of what happens with recursive dictionaries, and it can, just about, make sense because the methods do some work before recursing. To implement the dfunD we must generate code for the superclass C [a], which we had better not get by superclass selection from the supplied argument: dfunD :: forall a. D [a] -> D [a] dfunD = \d::D [a] -> MkD (scsel d) .. Otherwise if we later encounter a situation where we have a [Wanted] dw::D [a] we might solve it thus: dw := dfunD dw Which is all fine except that now ** the superclass C is bottom **! The instance we want is: dfunD :: forall a. D [a] -> D [a] dfunD = \d::D [a] -> MkD (dfunC (scsel d)) ... ----- THE SOLUTION -------- The basic solution is simple: be very careful about using superclass selection to generate a superclass witness in a dictionary function definition. More precisely: Superclass Invariant: in every class dictionary, every superclass dictionary field is non-bottom To achieve the Superclass Invariant, in a dfun definition we can generate a guaranteed-non-bottom superclass witness from: (sc1) one of the dictionary arguments itself (all non-bottom) (sc2) an immediate superclass of a smaller dictionary (sc3) a call of a dfun (always returns a dictionary constructor) The tricky case is (sc2). We proceed by induction on the size of the (type of) the dictionary, defined by GHC.Tc.Validity.sizeTypes. Let's suppose we are building a dictionary of size 3, and suppose the Superclass Invariant holds of smaller dictionaries. Then if we have a smaller dictionary, its immediate superclasses will be non-bottom by induction. What does "we have a smaller dictionary" mean? It might be one of the arguments of the instance, or one of its superclasses. Here is an example, taken from CmmExpr: class Ord r => UserOfRegs r a where ... (i1) instance UserOfRegs r a => UserOfRegs r (Maybe a) where (i2) instance (Ord r, UserOfRegs r CmmReg) => UserOfRegs r CmmExpr where For (i1) we can get the (Ord r) superclass by selection from (UserOfRegs r a), since it is smaller than the thing we are building (UserOfRegs r (Maybe a). But for (i2) that isn't the case, so we must add an explicit, and perhaps surprising, (Ord r) argument to the instance declaration. TLDR: Вполне легально иметь осмысленный class A a class A a => B a instance A a => A a instance B a => B a Но в инстансе B гхц может захотеть достать из переданного B суперкласс A и получит боттом, вместо того чтобы взять fix instanceA и получить что-то осмысленное. В качестве фикса суперклассы можно доставать только из меньших по типу словарей, так что в instance X a => Y a использовать суперкласс X чтобы создать суперкласс Y нельзя.
Обсуждают сегодня