Почему контекст не учитывается при выборе экземпляра 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