Вопрос по haskell – Что такого плохого в OverlappingInstances?

17

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

Несколько лет назад я мог задавать этот вопрос всерьез: в конце концов, вы можете предоставить полезные экземпляры по умолчанию, а другие могут заменить их более конкретными, когда это необходимо, что может быть такого плохого в этом?

По пути я воспринял некоторую оценку точки зрения,OverlappingInstances действительно не очень чистый, и лучше избегать; главным образом вытекает из того факта, что он не очень хорошо обоснован теоретически, в отличие от других больших расширений.

Но, думая об этом, я не уверен, смогу ли я объяснить, что на самом деле так плохо в этом отношении другому человеку, если бы меня спросили.

То, что я ищу, - это конкретные примеры способов использованияOverlappingInstances может привести к плохим событиям, будь то подрыв системы типов или других инвариантов, или просто общая неожиданность или беспорядок.

Одна конкретная проблема, о которой я знаю, состоит в том, что она нарушает свойство, которое простое добавление или удаление импорта одного модуля не может изменить смысл вашей программы, поскольку при включенном расширении новое перекрытие экземпляров может быть тихо добавлено или удалено. Хотя я могу понять, почему это неприятно, я не понимаю, почему это ужасно ужасно.

Бонусный вопрос: если мы говорим о полезных, но не теоретически обоснованных расширениях, которые могут привести к плохим событиям, почемуGeneralizedNewtypeDeriving не получает такой же плохой рэп? Это потому, что негативные возможности легче локализовать; что легче понять, что может вызвать проблемы, и сказать: «Не делай этого»?

(Примечание: я бы предпочел, чтобы основная часть ответа была сосредоточена наOverlappingInstancesнеIncoherentInstances который требует меньше объяснений.)

РЕДАКТИРОВАТЬ: Есть также хорошие ответы на аналогичный вопросВот.

Ах я вижу. Я думаю, что это именно та часть, о которой я думал, не вполне обоснованная. Разные перспективы. glaebhoerl
Связанные с:stackoverflow.com/questions/10830757/… Lambda Fairy
ПлохаяGeneralizedNewtypeDeriving это ошибка реализации. В этом расширении как таковом нет ничего плохого, но ghc допускает его в тех случаях, когда его следует запретить. augustss
@illissius Я не говорил, что эту ошибку легко исправить, но это всего лишь ошибка реализации. ЕслиGeneralizedNewtypeDeriving Создавая реальные экземпляры вместо того, чтобы разыгрывать приведения типов, эта ошибка не возникала (именно так я реализовал ее 10 лет назад в Bluespec). augustss
@augustss, это так просто? Увидетьhackage.haskell.org/trac/ghc/ticket/5498, Саймон говорит, что он не знает простого синтаксического теста, чтобы определить, когда он должен быть запрещен, и что он требует новой теоретической работы и усовершенствований в средстве проверки типов, чтобы сделать его безопасным. glaebhoerl

Ваш Ответ

1   ответ
18

которого пытается придерживаться язык haskell, - это добавление дополнительных методов / классов или экземпляров в данный модуль, что не должно приводить к тому, что любые другие модули, зависящие от данного модуля, либо не компилируются, либо ведут себя по-разному (если зависимые модули являются использовать явные списки импорта).

К сожалению, это не работает с OverlappingInstances. Например:

Модуль А:

{-# LANGUAGE FlexibleInstances, OverlappingInstances, MultiParamTypeClasses, FunctionalDependencies #-}

module A (Test(..)) where

class Test a b c | a b -> c where
   test :: a -> b -> c

instance Test String a String where
    test str _ = str

Модуль Б:

module B where
import A (Test(test))

someFunc :: String -> Int -> String
someFunc = test

shouldEqualHello = someFunc "hello" 4

shouldEqualHello равно "привет" в модуле Б.

Теперь добавьте следующее объявление экземпляра в A:

instance Test String Int String where
    test s i = concat $ replicate i s

Было бы предпочтительным, если бы это не влияло на модуль B. Он работал до этого добавления и должен работать после. К сожалению, это не так.

Модуль B все еще компилируется, но сейчасshouldEqualHello теперь равняется"hellohellohellohello", Поведение изменилось, хотя ни один метод, который он первоначально использовал, не изменился.

Что еще хуже, нет способа вернуться к старому поведению, так как вы не можете не импортировать экземпляр из модуля. Как вы можете себе представить, это очень плохо для обратной совместимости, поскольку вы не можете безопасно добавлять новые экземпляры в класс, который использует перекрывающиеся экземпляры, поскольку это может изменить поведение кода, который использует модуль (особенно это верно, если вы пишете код библиотеки). Это хуже, чем ошибка компиляции, так как может быть очень трудно отследить изменение.

По моему мнению, единственное безопасное время для использования перекрывающихся экземпляров - это когда вы пишете класс, который, как вы знаете, никогда не будет нуждаться в дополнительных экземплярах. Это может произойти, если вы делаете какой-то хитрый код на основе типов.

Error: User Rate Limit Exceeded glaebhoerl
Error: User Rate Limit Exceeded glaebhoerl
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded

Похожие вопросы