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

Сиротные случаи в Хаскелле

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

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

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

Edit:

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

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

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

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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