Ошибка перекрывающихся экземпляров при попытке записать резервный экземпляр
Я пытаюсь сделать что-то похожее на расширенный трюк перекрытия, чтобы определить экземпляр с перекрывающимся поведением. Я пытаюсь вывести экземпляр для кортежа, который будет использовать экземпляр для поля fst
, если оно существует, в противном случае используйте экземпляр для поля snd
, если оно существует. Это в конечном счете приводит к кажущейся неправильной ошибке о перекрывающихся экземплярах.
OverlappingInstances
.
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
Я тоже используя поликинд Proxy
и уровень типа или, :||:
.
import Data.Proxy
type family (:||:) (a :: Bool) (b :: Bool) :: Bool
type instance (:||:) False a = a
type instance (:||:) True a = True
A
это очень простой класс, чтобы играть. ThingA
имеет экземпляр A
; ThingB
Нет.
class A x where
traceA :: x -> String
data ThingA = ThingA
data ThingB = ThingB
instance A ThingA where
traceA = const "ThingA"
Цель следующих частей-написать экземпляр A
для (x, y)
, который будет определен до тех пор, пока существует экземпляр A x
или A y
. Если есть экземпляр A x
, он вернет ("fst " ++) . traceA . fst
. Если естьне экземпляр A x
, но есть экземпляр B x
, он вернется ("snd " ++) . traceA . fst
.
Первым шагом будет создание функциональной зависимости для проверки наличия экземпляра A
путем сопоставления с заголовком экземпляра. Это обычный подход из статьи advanced overlap.
class APred (flag :: Bool) x | x -> flag
instance APred 'True ThingA
instance (flag ~ 'False) => APred flag x
Если мы можем определить, имеют ли x
и y
оба экземпляра A
, мы можем определить, будет ли у (x, y)
один.
instance (APred xflag x, APred yflag y, t ~ (xflag :||: yflag)) => APred t (x, y)
Теперь я собираюсь отойти от простого примера в расширенном перекрытии и ввести вторую функциональную зависимость чтобы выбрать, следует ли использовать экземпляр A x
или A y
. (Мы могли бы использовать другой вид, чем Bool
для Chooses
и SwitchA
, чтобы избежать путаницы с APred
.)
class Chooses (flag :: Bool) x | x -> flag
Если есть экземпляр A x
, мы всегда выберем 'True
, в противном случае 'False
.
instance (APred 'True x) => Chooses 'True (x, y)
instance (flag ~ 'False) => Chooses flag (x, y)
Затем, как и в расширенном примере перекрытия, я определяю класс, идентичный A
, за исключением дополнительной переменной типа для выбора и аргумента Proxy
для каждого члена.
class SwitchA (flag :: Bool) x where
switchA :: Proxy flag -> x -> String
Это легко определить экземпляры ибо
instance (A x) => SwitchA 'True (x, y) where
switchA _ = ("fst " ++) . traceA . fst
instance (A y) => SwitchA 'False (x, y) where
switchA _ = ("snd " ++) . traceA . snd
Наконец, если существует экземпляр SwitchA
для того же типа, который (x, y)
Chooses
, мы можем определить экземпляр A (x, y)
.
instance (Chooses flag (x, y), SwitchA flag (x, y)) => A (x, y) where
traceA = switchA (Proxy :: Proxy flag)
Все до сих пор прекрасно компилируется. Однако, если я попытаюсь добавить
traceA (ThingA, ThingB)
Я получаю следующую ошибку.
Overlapping instances for Chooses 'True (ThingA, ThingB)
arising from a use of `traceA'
Matching instances:
instance APred 'True x => Chooses 'True (x, y)
-- Defined at defaultOverlap.hs:46:10
instance flag ~ 'False => Chooses flag (x, y)
-- Defined at defaultOverlap.hs:47:10
In the first argument of `print', namely
`(traceA (ThingA, ThingA))'
Что здесь происходит? Почему эти экземпляры перекрываются при поиске экземпляра для Chooses 'True ...
; не должен ли экземпляр instance flag ~ 'False => Chooses flag ...
не совпадать, когда flag
уже известен как 'True
?
Наоборот, если я попытаюсь
traceA (ThingB, ThingA)
Я получаю ошибку
No instance for (A ThingB) arising from a use of `traceA'
In the first argument of `print', namely
`(traceA (ThingB, ThingA))'
Любое понимание того, что происходит, когда я пытаюсь заставить компилятор делать то, что он не должен делать, было бы полезно.
Edit-Упрощение
Основываясь на наблюдении из этого ответа , мы можем полностью избавиться от Chooses
и написать
instance (APred choice x, SwitchA choice (x, y)) => A (x, y) where
traceA = switchA (Proxy :: Proxy choice)
Это решает проблему для traceA (ThingB, ThingA)
1 ответ:
Чтобы увидеть, что происходит на самом деле, посмотрите на класс
Chooses
. В частности, обратите внимание, что он не ленив в случаеFalse
(т. е. когда он не может сразу определить, что он должен иметь значение true):chooses :: Chooses b x => x -> Proxy b chooses _ = Proxy >:t chooses (ThingA, ()) chooses (ThingA, ()) :: Proxy 'True >:t chooses (ThingB, ()) <interactive>:1:1: Warning: Couldn't match type 'True with 'False In the expression: chooses (ThingB, ())
Причина, Почему он не ленив, не так проста. Самый конкретный пример, который является
instance (APred 'True x) => Chooses 'True (x, y)
Сначала. Чтобы убедиться в этом, компилятор должен проверить
APred
. Здесьinstance APred 'True ThingA
не совпадает, потому что у вас естьThingB
. Таким образом, он попадает во 2-й экземпляр и объединяетflag
(в избирает) с ложным. Тогда ограничениеAPred 'True x
не может удержаться! Так что проверка типов не проходит. Ошибка типа, которую вы получили, немного странная, но я думаю, что это потому, что у вас не включены OverlappingInstances. Когда я включаю его с помощью вашего кода, я получаю следующее:Как и ожидалось, типы True и False не могут быть объединены.>traceA (ThingA, ThingA) "fst ThingA" >traceA (ThingB, ThingA) <interactive>:43:1: Couldn't match type 'True with 'False In the expression: traceA (ThingB, ThingA) In an equation for `it': it = traceA (ThingB, ThingA)
Исправление довольно простое. Преобразуйте классы в функции типа. Функции типа по существу эквивалентны, но "больше ленивый". Это очень волнистая рука-извините, у меня нет лучшего объяснения, почему это работает.
Теперь вы думаете: "о Нет, я должен переписать весь свой код, чтобы использовать семейства типов."Это не так, так как вы всегда можете "понизить" семейство типов до предиката класса с функциональной зависимостью:type family APred' x :: Bool where APred' ThingA = True APred' x = False type family Chooses' x :: Bool where Chooses' (x, y) = APred' x instance (Chooses' (x,y) ~ flag, SwitchA flag (x, y)) => A (x, y) where traceA = switchA (Proxy :: Proxy flag)
instance Chooses' x ~ b => Chooses b x
Теперь ваш исходный экземпляр
instance (Chooses flag (x, y), SwitchA flag (x, y)) => A (x, y) where ...
будет работать, как и ожидалось.>traceA (ThingA, ThingA) "fst ThingA" >traceA (ThingB, ThingA) "snd ThingA"