Ответ 1
GHC просто выбирает один, и это правильный выбор. Любые два словаря для одного и того же ограничения должны быть равны.
OverlappingInstances и IncoherentInstances в основном эквивалентны разрушающей способности; они оба теряют когерентность экземпляра по дизайну (любые два равных ограничения в вашей программе удовлетворяются одним и тем же словарем). OverlappingInstances дает вам немного больше возможностей для разработки, какие экземпляры будут использоваться в каждом конкретном случае, но это не так полезно, когда вы доходите до точки прохождения Dicts в качестве значений первого класса и так далее. Я рассматривал бы только использование OverlappingInstances, когда рассматриваю перекрывающиеся экземпляры, эквивалентные друг другу (например, более эффективную, но в остальном равную реализацию для определенного типа типа Int), но даже тогда, если я достаточно забочусь о производительности, чтобы написать эту специализированную реализацию, Это ошибка производительности, если она не используется, когда она может быть?
Короче говоря, если вы используете OverlappingInstances, вы отказываетесь от права задавать вопрос о том, какой словарь будет выбран здесь.
Теперь верно, что вы можете нарушить последовательность кодов без OverlappingInstances. Фактически вы можете сделать это без сирот и без каких-либо расширений, кроме FlexibleInstances (возможно, проблема в том, что определение "сирота" неверно, когда включено FlexibleInstances). Это очень давняя ошибка GHC, которая частично не устранена из-за того, что (а) она фактически не может вызывать сбои напрямую, насколько известно кому-либо, и (б) может быть много программ, которые фактически полагаются на наличие нескольких экземпляров для одного и того же ограничения в отдельных частях программы, и этого может быть трудно избежать.
Возвращаясь к основной теме, в принципе важно, чтобы GHC мог выбрать любой словарь, доступный для удовлетворения ограничения, потому что, хотя они должны быть равны, GHC может иметь более статическую информацию о некоторых из них, чем другие. Ваш пример немного слишком прост, чтобы быть иллюстративным, но представьте, что вы передали аргумент bar
; вообще GHC ничего не знает о словаре, переданном через Dict
, поэтому он должен рассматривать это как вызов неизвестной функции, но вы вызывали foo
в конкретном типе T
, для которого существовал Bar T
instance в области видимости, тогда GHC будет знать, что bar
из словаря ограничений Bar a
был T
bar
и мог генерировать вызов известной функции и, возможно, встроить T
bar
и сделать больше оптимизаций.
На практике GHC в настоящее время не является таким умным, и он просто использует самый доступный словарь. Возможно, было бы лучше всегда использовать внешний словарь. Но такие случаи, когда есть несколько доступных словарей, не очень распространены, поэтому у нас нет хороших тестов для тестирования.