Осиротевшие экземпляры в Хаскеле - PullRequest
81 голосов
/ 20 июня 2010

При компиляции моего приложения на Haskell с параметром -Wall GHC жалуется на потерянные экземпляры, например:

Publisher.hs:45:9:
    Warning: orphan instance: instance ToSElem Result

Тип класса ToSElem не мой, он определяется HStringTemplate .

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

Причина, по которой я хочу объявить свои экземпляры ToSElem в модуле Publisher, заключается в том, что именно модуль Publisher зависит от HStringTemplate, а не от других модулей. Я пытаюсь поддерживать разделение интересов и избегать зависимости каждого модуля от HStringTemplate.

Я подумал, что одним из преимуществ классов типов Haskell по сравнению, например, с интерфейсами Java, является то, что они открыты, а не закрыты, и поэтому экземпляры не должны объявляться в том же месте, что и тип данных. Совет GHC, похоже, игнорирует это.

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

Ответы [ 6 ]

88 голосов
/ 20 июня 2010

Я понимаю, почему вы хотите это сделать, но, к сожалению, это может быть только иллюзией, что классы на Хаскеле кажутся "открытыми" в том смысле, как вы говорите. Многие люди считают, что возможность сделать это является ошибкой в ​​спецификации Haskell, по причинам, которые я объясню ниже. В любом случае, если это действительно не подходит для экземпляра, вам нужно объявить либо в модуле, где объявлен класс, либо в модуле, где объявлен тип, это, вероятно, признак того, что вы должны использовать newtype или какая-то другая обертка вокруг вашего типа.

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

Проблема связана с тем фактом, что, когда существует несколько объявлений экземпляров для одного и того же класса и типа, в стандартном Haskell нет механизма, определяющего, какой из них использовать. Скорее программа отклонена компилятором.

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

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

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

Так что же делать с этой проблемой? Лагерь анти-сироты говорит, что предупреждение GHC - это ошибка, это должна быть ошибка, которая отклоняет любую попытку объявить экземпляр-сироту. Тем временем мы должны проявлять самодисциплину и избегать их любой ценой.

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

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

Мой вывод из всего этого состоит в том, что, по крайней мере, на данный момент, я настоятельно рекомендую вам избегать объявления каких-либо сиротских случаев, чтобы быть внимательным к другим, если ни по какой другой причине. Используйте newtype.

39 голосов
/ 20 июня 2010

Иди и подавь это предупреждение!

Вы в хорошей компании. Конал делает это в "TypeCompose". это делают "chp-mtl" и "chp-transformers", "control-monad-exception-mtl" и "control-monad-exception-monadsfd" и т. д.

Кстати, вы, наверное, уже знаете это, но для тех, кто не ставит ваш вопрос в поиске:

{-# OPTIONS_GHC -fno-warn-orphans #-}

Edit:

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

Я использовал только восклицательный знак в своем коротком ответе, потому что ваш вопрос показывает, что вы уже хорошо знаете о проблемах. Иначе я бы был менее восторженным :)

Немного отвлекает, но я считаю, что это идеальное решение в идеальном мире без компромиссов:

Я считаю, что проблемы, о которых упоминает Иц (не зная, какой экземпляр выбран), могут быть решены в "целостной" системе программирования, где:

  • Вы не редактируете простые текстовые файлы примитивно, а скорее оказывает помощь в среде (например, завершение кода предлагает только вещи соответствующих типов и т. Д.)
  • Язык "более низкого уровня" не имеет специальной поддержки для классов типов, и вместо этого таблицы функций передаются явно
  • Но среда программирования «более высокого уровня» отображает код аналогично тому, как теперь представлен Haskell (вы обычно не видите переданные таблицы функций), и выбирает для вас явные классы типов, когда они очевидно (например, во всех случаях Functor есть только один выбор), и когда есть несколько примеров (почтовый список Applicative или list-monad Applicative, First / Last / lift может быть Monoid), он позволяет вам выбрать, какой экземпляр использовать.
  • В любом случае, даже когда экземпляр был выбран для вас автоматически, среда легко позволяет вам увидеть, какой экземпляр был использован, с простым интерфейсом (гиперссылкой или парящим интерфейсом или чем-то еще)

Вернитесь из фэнтезийного мира (или, надеюсь, из будущего) прямо сейчас: я рекомендую стараться избегать сиротских случаев, все еще используя их, когда вам «действительно нужно», до

35 голосов
/ 21 июня 2010

Сиротские экземпляры - это неприятность, но, на мой взгляд, они иногда необходимы. Я часто комбинирую библиотеки, в которых тип происходит из одной библиотеки, а класс - из другой библиотеки. Конечно, нельзя ожидать, что авторы этих библиотек предоставят экземпляры для каждой мыслимой комбинации типов и классов. Поэтому я должен предоставить их, и поэтому они сироты.

Идея о том, что вы должны обернуть тип в новый тип, когда вам нужно предоставить экземпляр, является идеей с теоретической значимостью, но во многих случаях она просто слишком утомительна; Это идея, выдвинутая людьми, которые не пишут код на Haskell для жизни. :)

Так что давай, давай сиротские экземпляры. Они безвредны.
Если вы можете аварийно завершить работу ghc с потерянными экземплярами, то это ошибка, о которой следует сообщить как таковой. (Ошибка, которую ghc имел / имеет о том, что не обнаруживает несколько экземпляров, не так сложно исправить.)

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

17 голосов
/ 21 июня 2010

В этом случае я думаю, что использование экземпляров-сирот - это нормально. Основное правило для меня таково: вы можете определить экземпляр, если вы «владеете» классом типов или «владеете» типом данных (или некоторым их компонентом, т. Е. Экземпляр Maybe MyData тоже подойдет по крайней мере иногда). В рамках этих ограничений, когда вы решаете поместить экземпляр, это ваше личное дело.

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

5 голосов
/ 17 февраля 2011

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

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

3 голосов
/ 22 июня 2010

Кроме того, я понимаю, что библиотеки WRT с позицией лагеря анти-сиротских экземпляров, но для исполняемых целей, не должны ли быть хорошими экземпляры-сироты?

...