Осиротевшие экземпляры в Haskell

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

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

Класс типа ToSElem не является моим, это определяется HStringTemplate.

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

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

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

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

82
задан Dan Dyer 20 June 2010 в 14:17
поделиться

5 ответов

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

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

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

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

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

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

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

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

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

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

89
ответ дан 24 November 2019 в 09:09
поделиться

Продолжайте и подавите это предупреждение!

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

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

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

Изменить:

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

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

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

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

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

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

41
ответ дан 24 November 2019 в 09:09
поделиться

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

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

17
ответ дан 24 November 2019 в 09:09
поделиться

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

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

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

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

35
ответ дан 24 November 2019 в 09:09
поделиться

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

3
ответ дан 24 November 2019 в 09:09
поделиться
Другие вопросы по тегам:

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