Ошибка перекрывающихся экземпляров при попытке записать резервный экземпляр


Я пытаюсь сделать что-то похожее на расширенный трюк перекрытия, чтобы определить экземпляр с перекрывающимся поведением. Я пытаюсь вывести экземпляр для кортежа, который будет использовать экземпляр для поля 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 5

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. Когда я включаю его с помощью вашего кода, я получаю следующее:

>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)
Как и ожидалось, типы True и False не могут быть объединены.

Исправление довольно простое. Преобразуйте классы в функции типа. Функции типа по существу эквивалентны, но "больше ленивый". Это очень волнистая рука-извините, у меня нет лучшего объяснения, почему это работает.

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"