2012-09-27 3 views
16

Я разрабатываю библиотеку, которая в значительной степени выиграет от использования флага компилятора OverlappingInstances. Но everyone говорит smack об этом расширении и предупреждает об его опасностях. Мой вопрос в том, есть примеры хорошего использования этого расширения в любом месте хакера? Есть ли какое-либо эмпирическое правило о том, как инкапсулировать плохость и правильно использовать расширение?Есть ли хорошие варианты использования OverlappingInstances?

ответ

30

Возможно, мысленный эксперимент немного устранит это расширение.

Давайте сделаем вид, что мы отбросили ограничение, что функции, определенные с помощью нескольких паттернов, должны находиться в одном месте, чтобы вы могли написать foo ("bar", Nothing) = ... в верхней части модуля, а затем иметь такие случаи, как foo ("baz", Just x) = ... в другом месте. На самом деле, давайте пойдем еще дальше, и разрешим, чтобы случаи были определены в разных модулях полностью!

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

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

  • Anywhere используется такая функция, аргументы, приведенные должен соответствовать ровно один шаблону. Все остальное - ошибка компилятора.
  • Добавление новых шаблонов (в том числе путем импорта другого модуля) никогда не должно изменять значение допустимого кода - либо выбраны одинаковые шаблоны, либо возникает ошибка компилятора.

Должно быть ясно, что соответствующие простые конструкторы, такие как True или Nothing, просты. Мы также можем немного переносить вещи и предположить, что компилятор может устранить пробелы в литературе, например, "bar" и "baz".

С другой стороны, связывание аргументов с узорами, как (x, Just y) становится неудобно - написание такой картины означает отказ возможность писать картины, как (True, _) или (False, Just "foobar") позже, так как это может создать неопределенность. Хуже того, стражники становятся почти бесполезными, потому что им нужны очень общие матчи. Многие распространенные идиомы будут приводить к бесконечным двусмысленным головным болям, и, конечно, писать «дефолтный» всплеск картины совершенно невозможно.

Это примерно такая же ситуация с экземплярами класса типов.

Мы могли бы восстановить некоторую выразительную силу, расслабляя требуемые свойства, как, например:

  • Везде используются такая функция, она должна соответствовать по крайней мере один шаблона. Нет совпадений - ошибка компилятора.
  • Если функция используется так, чтобы совпадало несколько шаблонов, будет использоваться шаблон наиболее подходящего образца. Если нет уникального наиболее конкретного шаблона, возникает ошибка.
  • Если функция используется таким образом, который соответствует общему экземпляру, но может применяться во время выполнения к аргументам, которые будут соответствовать более конкретному экземпляру, это ошибка компилятора.

Обратите внимание, что теперь мы находимся в ситуации, когда простое импортирование модуля может изменить поведение функции, введя в область новый более конкретный шаблон. В сложных случаях, в том числе и с функциями более высокого порядка, могут быть мутные. Тем не менее, во многих случаях проблемы маловероятны - скажем, определение общего падающего шаблона в библиотеке, в то же время позволяя клиентскому коду при необходимости добавлять конкретные случаи.

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

В чем дело, так это то, что ограничения, устраняемые OverlappingInstances, заключаются в том, что использование классов типов ведет себя разумно в соответствии с предположением «открытого мира» о том, что позже может быть добавлен любой возможный экземпляр. Ослабляя эти требования, вы сами берете на себя это бремя; поэтому подумайте о том, как можно добавить новые экземпляры, и является ли какой-либо из этих сценариев серьезной проблемой. Если вы уверены, что ничто не сломается даже в неясных и коварных угловых случаях, тогда продолжайте использовать расширение.

+1

Это действительно отличный ответ. –

+0

Согласен. Это проклятый приятный ответ. – MathematicalOrchid

12

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

Однако вы все еще можете «инкапсулировать добро», используя новые типы. Учитывая следующее определение экземпляра, который склонен к перекрывающихся случаях:

instance (SomeConstraint a) => SomeClass a where ... 

Вы можете использовать вместо этого:

newtype N a = N { unN :: a } 

instance (SomeConstraint a) => SomeClass (N a) where ... 

система типа класса В настоящее время в Haskell имеет правильный тип в соответствии с (т.е. N a), вместо того, чтобы безвозмездно сопоставлять каждый отдельный тип. Это позволяет вам контролировать область действия экземпляра, поскольку теперь будут совпадать только те вещи, которые завернуты в новый тип N.

+3

Существует ли теоретическая основа для отсутствия ориентированного на ограничение вывода? –

+2

Это самый ясный и понятный способ, которым я когда-либо читал этот совет. Он должен быть помещен во все учебники. – AndrewC

+0

У меня есть googled «ограничение, связанное с ограничениями», но ничего не получилось. Можете ли вы рассказать о концепции? – CMCDragonkai

2

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

class TypeEq (a :: k) (b :: k) (t :: Bool) | a b -> t where 
    typeEq :: Proxy a -> Proxy b -> HBool t 

в настоящее время это может быть реализовано (полностью родовым образом) с OverlappingInstance. Примеры использования включают кодирование Олегом ООП в Haskell. Таким образом, мой один пример хорошего использования OverlappingInstances является this implementation of TypeEq from the classic HList paper

Эта конкретная функциональность может быть обеспечена очень тривиальным с поддержкой компилятор (и даже работать в функции типа, а не на уровне fundep) и, таким образом, прикрепляя один модуль с TypeEq где-то мне не кажется так плохо.

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

+0

Обратите внимание, что 'TypeEq' может использоваться из неперекрывающихся экземпляров, поэтому действительно можно выделить использование расширения. Вы не можете использовать его с семействами типов, но это другая проблема. В стороне «IncoherentInstances» грубо означает сбросить треть из «смягченных» свойств в моем ответе. Вместо того, чтобы требовать, чтобы выбор экземпляра был согласованным в отношении фактического использования и жалуясь на двусмысленность, GHC просто пожимает плечами и выбирает, какой экземпляр выглядит лучше всего в то время. Ура! –

Смежные вопросы