Как определить разумные границы зависимостей пакета при выпуске библиотеки Haskell?

Когда вы выпускаете библиотеку в Hackage, как я могу определить разумные границы для моих зависимостей?

Это очень короткий вопрос - не знаю, какую дополнительную информацию я могу предоставить.

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


По существу мой вопрос относится к ограничениям, которые в настоящее время установлены как:

library
  hs-source-dirs: src
  default-language: Haskell2010
  exposed-modules: Data.ByteUnits
  build-depends:       base >=4.9 && <4.10
                       , safe == 0.3.15

Я не думаю, что == - хорошая идея.

Ответ 1

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

  • Посмотрите на версию зависимостей, которые вы используете в настоящее время, например. safe-0.3.15. Предположим, что пакет соответствует PVP и не будет выпускать прерывание до версии 0.4 и добавит следующее: safe >= 0.3.15 && < 0.4
  • Вышеприведенное замечательно, но ограничивает множество потенциально допустимых планов сборки. Вы можете провести тестирование времени с другими версиями зависимостей. Например, если вы протестируете около 0.2.12 и 0.4.3, и оба они работают, вы можете перейти на safe >= 0.2.12 && < 0.5.
    • ПРИМЕЧАНИЕ. Общей ошибкой, которая возникает, является то, что в будущей версии вашего пакета вы забываете проверить совместимость со старыми версиями, и, оказывается, вы используете новую функцию, представленную в скажем, safe-0.4.1, что делает старые ограничения недействительными. К сожалению, не так много средств автоматизации для проверки этого.
  • Просто забудьте все это: никаких ограничений в отношении версий и не отвечайте за потребитель пакета, чтобы обеспечить совместимость в плане сборки. Это имеет недостаток, что можно создать недопустимые планы сборки, но потенциал роста, который ваши ограничения не устранит потенциально хороших. (Это в основном ложный положительный результат против ложных отрицательных компромиссов.)

Проект Stackage запускает ночные сборки, которые могут часто сообщать вам, когда ваш пакет поврежден новыми версиями зависимостей, и упростить его пользователи потребляют ваш пакет, предоставляя предварительно созданные снимки, которые, как известно, работают. Это особенно помогает в случае (3) и немного с ослабленными нижними границами в (2).

Вы также можете рассмотреть возможность использования конфигурации Travis для тестирования старых снимков Stackage, например. https://github.com/commercialhaskell/stack/blob/master/doc/travis-complex.yml

Ответ 2

Предполагаю, что вы знаете Haskell Package Versioning Policy (PVP). Это дает некоторые рекомендации, как неявно в том значении, которое он присваивает первым трем компонентам версии ( "A.B.C" ), так и некоторым явным советам по диапазонам версий Cabal.

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

import qualified Something as S
import Something (foo, bar)

вы можете смело написать зависимость формы:

something >= 1.2.0 && < 1.6

где предположение состояло бы в том, что вы протестировали 1.2.0 через 1.5.6, скажем, и уверены, что он будет продолжать работать со всеми будущими 1.5.x (неразрывными изменениями), но возможно перерыв в будущем 1.6.

Если вы импортировали пакет неквалифицированный (что может быть очень хорошо, если вы повторно экспортируете большой кусок его API), вам понадобится вариант:

the-package >= 1.2.0 && < 1.5.4   -- tested up to 1.5.3 API
the-package >= 1.5.3 && < 1.5.4   -- actually, this API precisely

Существует также оговорка (см. PVP), если вы определяете экземпляр сирота.

Наконец, при импорте некоторых простых, стабильных пакетов, в которых вы импортировали только наиболее очевидные стабильные компоненты, вы, вероятно, могли бы предположить, что:

the-package >= 1.2.0 && < 2

будет довольно безопасным.

Глядя на файл Cabal для большого, сложного, хорошо написанного пакета, вы можете получить представление о том, что сделано на практике. Пакет lens, например, широко использует зависимости формы:

array >= 0.3.0.2 && < 0.6

но имеет случайные зависимости вроде:

free >= 4 && < 6

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

Ответ 3

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

build-depends:    foo >= X && < Y

В идеале, функция, которую вам никогда не нужно удалять, в этом случае вы можете сбросить верхнюю границу. Это означает, что верхняя граница нужна только тогда, когда вы знаете, что ваша функция исчезает из более поздней версии. В противном случае предположим, что foo >= X является достаточным, пока у вас не будет доказательств обратного.

foo == X редко следует использовать; он в основном не подходит для foo >= X && <= X и указывает, что вы используете функцию, которая находится только в версии X; это было не в более ранних версиях, и оно было удалено в более поздней версии. Если вы окажетесь в такой ситуации, скорее всего, лучше попытаться переписать код, чтобы больше не полагаться на эту функцию, чтобы вы могли вернуться к использованию foo >= Z (отделив требование к версии X в точности, вы можете пройти с еще более ранней версией Z < X foo).

Ответ 4

Ответ на "надежный" будет: разрешить именно те версии, которые, как вы уверены, будут работать успешно! Если вы только что скомпилировали свой проект с помощью safe-0.3.15, то, с технической точки зрения, вы не знаете, будет ли он работать с safe-0.3.15, поэтому ограничение, предлагаемое предложениями, является правильным. Если вам нужна совместимость с другими версиями, проверьте их, последовательно отступая назад. Это можно сделать проще всего, полностью отключив ограничение в файле .cabal, а затем сделав

$ cabal configure --constraint='safe==XYZ' && cabal test

Для каждой версии XYZ = 0.3.14 и т.д.

Практически говоря, это немного параноидальный подход. В частности, это хороший этикет для пакетов, которые следуют Политика версий пакетов, которая требует, чтобы новые младшие версии никогда не нарушали никаких сборок. I.e, если 0.3.15 работает, тогда 0.3.16 и т.д. Также должны работать. Таким образом, консервативное ограничение, если вы проверили только 0.3.15, было бы safe >=0.3.15 && <0.4. Вероятно, safe >=0.3 && <0.4 был бы безопасным & dagger; тоже. PVP также требует, чтобы вы не использовали более слабые ограничения главной версии, чем вы можете подтвердить, чтобы работать, т.е. Он задает ограничение <0.4.

Часто это по-прежнему бесполезно строго. Это зависит от того, насколько сильно вы работаете с каким-то пакетом. В частности, иногда вам нужно явно зависеть от пакета только для некоторой дополнительной функции конфигурации типа, используемого более важной зависимостью. В таком случае я, как правило, не даю никаких ограничений для вспомогательного пакета. В крайнем случае, если я полагаюсь на diagrams-lib, нет никаких оснований давать какие-либо оценки diagrams-core, потому что это все равно связано с diagrams-lib.

Я также обычно не беспокоюсь о границах для очень стабильных и стандартных пакетов, таких как containers. Исключение составляет, конечно, base.


& dagger; Вам нужно было выбрать пакет safe в качестве примера?