Ответ 1
Проблема возникает, когда у нас уже установлены пакеты B и C, но они построены для разных версий D, а затем мы пытаемся использовать оба пакета B и C вместе в пакете A: Проблема диамантной зависимости Это может работать нормально, но только если пакеты B и C не отображают типы, определенные в D в своих интерфейсах. Если они это сделают, пакет A не сможет использовать функции из B и C вместе, потому что они не будут работать с одним и тем же типом. То есть вы получите ошибку типа.
Чтобы выбрать конкретный пример, предположим, что пакет D является байтовым, и мы установили как bytestring-0.9.0.1, так и 0.9.0.4. Допустим, что B - это utf8-строка, а C - регулярное выражение. Допустим, что пакет A - это программа Yi editor. Итак, дело в том, что в каком-то месте в коде в Yi мы хотим передать bytestring, созданный в результате декодирования UTF-8, в качестве входных данных для одной из функций регулярного выражения. Но это не работает, потому что функции в пакете utf8-string используют тип ByteString из bytestring-0.9.0.1, в то время как функции regex в пакете regex используют тип ByteString из bytestring-0.9.0.4. Таким образом, мы получаем ошибку типа, когда пытаемся скомпилировать Yi:
Не удалось сопоставить ожидаемый тип bytestring-0.9.0.4:Data.ByteString.ByteString'
against inferred type
bytestring-0.9.0.1: Data.ByteString.ByteString '
Что касается GHC, эти два типа абсолютно не связаны!
Это, безусловно, крайне раздражает. Также нет простого решения. В этом примере мы предполагаем, что пакеты B и C уже построены, поэтому на самом деле нет способа разумно использовать эти два пакета вместе, не перестраивая их с другой версией пакета D. В этом случае очевидное решение перестроить B для использования D-1.1, а не D-1.0. Конечно, проблема с переустановкой пакета - это разрывает все другие пакеты, которые уже были построены против него. Непонятно, что вы хотите, чтобы менеджер пакетов автоматически перестраивал множество явно не связанных пакетов.
В долгосрочной перспективе лучшим решением было бы сделать то, что делает Nix. В приведенном выше примере вместо замены пакета B, построенного против D-1.0 с B, построенного против D-1.1, Nix добавит еще один экземпляр B, построенный против D-1.1. Таким образом, исходный экземпляр B останется неизменным и ничто не сломается. Это функциональный подход: мы никогда не изменяем значения (установленные пакеты), мы просто создаем новые, а мусор собираем старый, когда они больше не нужны.
На практике это означает, что мы должны идентифицировать установленные пакеты с использованием некоторого хэша пакета и хэшей всех зависимых пакетов. jhc уже делает это, и есть шаги, чтобы сделать что-то подобное для GHC, хотя больше нацелено на отслеживание изменений API/ABI. Для правильного управления пакетами на основе исходных данных я считаю это правильным направлением.
Я должен отметить, что это не новая проблема. Вы смогли создать эту проблему с тех пор, как ghc начал устанавливать сразу несколько версий одного и того же пакета. Мы просто замечаем это гораздо чаще, потому что мы разбиваем базовый пакет и позволяем обновлять эти распакованные пакеты.
Текущее состояние игры - это то, что Cabal предупреждает об этой проблеме, но на самом деле не помогает вам ее решить. В приведенном выше примере мы получим:
$ cabal configure
Configuring A-1.0...
Warning: This package indirectly depends on multiple versions of
the same package. This is highly likely to cause a compile failure.
package B-1.0 requires D-1.0
package C-1.0 requires D-1.1