Подтвердить что ты не робот

Есть ли хорошие примеры использования OverlappingInstances?

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

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

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

newtype N a = N { unN :: a }

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

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

Ответ 3

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

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

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

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

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