Первый: во время выбора экземпляра имеет значение только голова экземпляра: то, что слева от =>
, не имеет значения. Таким образом, instance D a a
предотвращает выбор, если они не равны; instance ... => D u a
всегда можно выбрать.
Теперь, прагмы перекрытия вступают в игру только в том случае, если один экземпляр уже более "специфичен", чем другой. «Специфично» в данном случае означает «если существует замена переменных типа, которые могут создавать экземпляр экземпляра A
для заголовка экземпляра B
, то B
является более специфичным, чем A
». В
instance D a a
instance {-# OVERLAPPING #-} D u (f x)
ни один не является более конкретным, чем другие, поскольку нет замены a := ?
, которая превращает D a a
в D u (f x)
, и нет никакой замены u := ?; f := ?; x := x
, которая превращает D u (f x)
в D a a
. Прагма {-# OVERLAPPING #-}
ничего не делает (по крайней мере, относится к проблеме). Таким образом, при разрешении ограничения D [Integer] [Integer]
компилятор находит оба экземпляра как кандидатов, не более конкретных, чем другие, и выдает ошибку.
В
instance (u ~ a) => D u a
instance {-# OVERLAPPING #-} D u (f x)
второй экземпляр более специфичен, чем первый, потому что первый экземпляр можно создать с помощью u := u; a := f x
, чтобы перейти ко второму. Прагма теперь тянет на себя. При разрешении D [Integer] [Integer]
оба экземпляра совпадают, первый с u := [Integer]; a := [Integer]
, а второй с u := [Integer]; f := []; x := Integer
. Однако второй является более конкретным и OVERLAPPING
, поэтому первый отбрасывается как кандидат, а второй используется. (Примечание: я думаю, что экземпляр first должен быть OVERLAPPABLE
, а второй экземпляр не должен иметь прагмы. Таким образом, все будущие экземпляры неявно перекрывают экземпляр catch-all вместо необходимости аннотировать каждый один.)
С помощью этого трюка выбор выполняется с правильным приоритетом, а затем равенство между двумя аргументами все равно будет принудительным. Эта комбинация, по-видимому, дает то, что вы хотите.
Одним из способов визуализации происходящего является диаграмма Венна. С первой попытки instance D a a
и instance D u (f x)
образуют два набора, наборы пар типов, которым может соответствовать каждый. Эти наборы перекрываются, но существует множество пар типов только с совпадениями D a a
и много пар только с D u (f x)
совпадениями. Ни один из них не может быть назван более конкретным, поэтому прагма OVERLAPPING
не удалась. Во второй попытке D u a
фактически охватывает всю вселенную пар типов, а D u (f x)
является ее подмножеством (читай: внутри). Теперь прагма OVERLAPPING
работает. Мышление таким образом также показывает нам другой способ сделать эту работу, создав новый набор, который точно покрывает пересечение первой попытки.
instance D a a
instance D u (f x)
instance {-# OVERLAPPING #-} (f x) (f x)
Но я бы выбрал тот с двумя экземплярами, если вам действительно не нужно использовать это по какой-то причине.
Обратите внимание, однако, что перекрывающиеся экземпляры считаются немного хрупкими. Как вы заметили, часто сложно понять, какой экземпляр выбран и почему. Нужно рассмотреть все экземпляры в области видимости, их приоритеты и, по сути, запустить нетривиальный алгоритм выбора, чтобы понять, что происходит. Когда экземпляры определены для нескольких модулей (включая сирот), все становится еще сложнее, поскольку правила выбора могут отличаться в зависимости от локального импорта. Это может даже привести к непоследовательности. Лучше всего избегать их, когда это возможно.
См. Также руководство GHC .