Ответ 1
Я не вижу опубликованной версии синтаксиса, чья подпись для sugarSym
использует эти точные имена типов, поэтому я буду использовать ветку разработки при фиксации 8cfd02 ^, последняя версия, которая все еще использовала эти имена.
Итак, почему GHC жалуется на fi
в вашей сигнатуре типа, но не на sugarSym
? В документации, с которой вы ссылались, объясняется, что тип является неоднозначным, если он не отображается справа от ограничения, если только ограничение не использует функциональные зависимости для вывода неоднозначного типа из других недвусмысленных типов. Поэтому давайте сравним контексты двух функций и ищем функциональные зависимости.
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
Итак, для sugarSym
недвусмысленные типы: sub
, sig
и f
, и из них мы должны иметь возможность следить за функциональными зависимостями, чтобы устранить все другие типы, используемые в контексте, а именно sup
и fi
. И действительно, функциональная зависимость f -> internal
в SyntacticN
использует наш f
для устранения двусмысленности нашего fi
, и после этого функциональная зависимость f -> sig sym
в ApplySym
использует наш недавно неоднозначный fi
для устранения неоднозначности sup
(и sig
, что уже не было двусмысленным). Таким образом, это объясняет, почему sugarSym
не требует расширения AllowAmbiguousTypes
.
Теперь посмотрим на sugar
. Первое, что я замечаю, это то, что компилятор не жалуется на двусмысленный тип, а скорее на перекрывающиеся экземпляры:
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
Итак, если я читаю это правильно, не то, что GHC считает, что ваши типы неоднозначны, а скорее, что, проверяя, являются ли ваши типы неоднозначными, GHC столкнулся с другой отдельной проблемой. Затем он говорит вам, что если вы сказали GHC не выполнять проверку двусмысленности, это не столкнулось бы с этой отдельной проблемой. Это объясняет, почему включение AllowAmbiguousTypes позволяет компилировать ваш код.
Однако проблема с перекрывающимися экземплярами остается. Два экземпляра, перечисленные GHC (SyntacticN f fi
и SyntacticN (a -> f) ...
), перекрываются друг с другом. Как ни странно, похоже, что первая из них должна пересекаться с любым другим экземпляром, который является подозрительным. А что означает [overlap ok]
?
Я подозреваю, что Syntactic скомпилирован с OverlappingInstances. И, глядя на код, он действительно делает.
Экспериментируя немного, кажется, что GHC в порядке с перекрывающимися экземплярами, когда ясно, что он строго более общий, чем другой:
{-# 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])
Но GHC не в порядке с перекрывающимися экземплярами, когда ни один из них явно лучше подходит, чем другой:
{-# 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])
В вашей сигнатуре типа используется SyntacticN (a -> (a -> b) -> b) fi
, и ни SyntacticN f fi
, ни SyntacticN (a -> f) (AST sym (Full ia) -> fi)
не лучше, чем другие. Если я изменю эту часть вашей сигнатуры типа на SyntacticN a fi
или SyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi)
, GHC больше не будет жаловаться на перекрытие.
Если бы я был вами, я бы посмотрел на определение этих двух возможных экземпляров и определил, является ли одна из этих двух реализаций той, которая вам нужна.