Почему контекст не учитывается при выборе экземпляра typeclass в Haskell?

Я понимаю, что, когда

instance (Foo a) => Bar a
instance (Xyy a) => Bar a

GHC не рассматривает контексты, и экземпляры сообщаются как дубликаты.

Что является противоречивым, что (я думаю), после выбора экземпляра, все равно нужно проверить, соответствует ли контекст, а если нет - отменить экземпляр. Итак, почему бы не изменить порядок и отбросить экземпляры с несогласованными контекстами и продолжить работу с оставшимся набором.

Неужели это будет трудноразрешимым? Я вижу, как это может привести к более высокой разрешающей способности работы, но так же, как есть UndecidableInstances/IncoherentInstances, не может быть ConsiderInstanceContexts, когда "Я знаю, что делаю"?

Ответ 1

Это не отвечает на вопрос, почему это так. Обратите внимание, однако, что вы всегда можете определить оболочку newtype для устранения неоднозначности между двумя экземплярами:

newtype FooWrapper a = FooWrapper a
newtype XyyWrapper a = XyyWrapper a

instance (Foo a) => Bar (FooWrapper a)
instance (Xyy a) => Bar (XyyWrapper a)

Это имеет дополнительное преимущество, что, обойдя FooWrapper или XyyWrapper, вы явно контролируете, какой из двух экземпляров вы хотели бы использовать, если ваш а будет удовлетворять обоим.

Ответ 2

Классы немного странные. Первоначальная идея (которая по-прежнему в значительной степени работает) является своего рода синтаксическим сахаром вокруг того, что в противном случае было бы data. Например, вы можете себе представить:

data Num a = Num {plus :: a -> a -> a, ... , fromInt :: Integer -> a}
numInteger :: Num Integer
numInteger = Num (+) ... id

тогда вы можете писать функции, которые, например, Тип:

test :: Num x -> x -> x -> x -> x
test lib a b c = a + b * (abs (c + b))
    where (+) = plus lib
          (*) = times lib
          abs = absoluteValue lib

Итак, идея состоит в том, что "мы собираемся автоматически получить весь этот код библиотеки". Вопрос в том, как мы находим нужную библиотеку? Это легко, если у нас есть библиотека типа Num Int, но как ее расширить до "ограниченных экземпляров" на основе функций типа:

fooLib :: Foo x -> Bar x
xyyLib :: Xyy x -> Bar x

существующее решение в Haskell - это выполнить сопоставление типа-типа в выходных типах этих функций и распространить входы на итоговое объявление. Но когда есть два выхода одного типа, нам нужен комбинатор, который объединяет их в:

eitherLib :: Either (Foo x) (Xyy x) -> Bar x

и в основном проблема заключается в том, что сейчас нет хорошего комбинатора ограничений такого рода. Это ваше возражение.

Ну, это правда, но есть способы добиться чего-то морально подобного на практике. Предположим, что мы определим некоторые функции с типами:

data F
data X
foobar'lib :: Foo x -> Bar' x F
xyybar'lib :: Xyy x -> Bar' x X
bar'barlib :: Bar' x y -> Bar x

Очевидно, что y является своего рода "phantom type", пронизывающим все это, но он остается мощным, поскольку, учитывая, что мы хотим Bar x, мы будем распространять необходимость в Bar' x y и учитывая необходимо для Bar' x y мы будем генерировать либо a Bar' x X, либо a Bar' x y. Таким образом, с классами phantom и классами с несколькими параметрами мы получаем желаемый результат.

Дополнительная информация: https://www.haskell.org/haskellwiki/GHC/AdvancedOverlap