Description
Problem Statement
Currently there is no satisfying way to express packages which
conflict with each other in the same install-plan.
Some limited form of conflicts can be expressed when two packages
depend directly on each other via build-depends
relations.
However, this is not enough to handle cases where it is necessary to
make sure that two or more packages are mutually exclusive to each
other.
This the need for such mutual exclusivity occurs when entities which
may need to be unique throughout the install-plan, such as
(desugared-into) typeclasses or relocated orphan instances are
involved.
Motivating Use-cases
Relocating orphan instances into compat-packages
One recent use-case which can't be solved properly currently is the
following:
semigroupoids-4.3
conditionally defines an orphaninstance Foldable (Either a)
(forbase<4.8
)- Starting with
base-4.8
,base
started providinginstance Foldable (Either a)
- Then
base-orphans-0.1
started equally providing an such an orphan instance likesemigroupoids-4.3
semigroupoids-5.0
now depends onbase-orphans
However, there's currently no way to express that the combination
base >= 4.8
semigroupoids < 5
, andbase-orphans
is forbidden to occur in an install-plan.
Alternative base
/Preludes
replacements with incompatible typeclass hierarchies
This use-case refers to a future not-yet implemented
ExportedRebindableSyntax
extension, which would allow the currently
in scope Prelude
module to export desugaring rules for things like
do
-syntax, and therefore allow to provide seamless Haskell2010
support (with a different Monad
class different from base
's
Monad
class) in recent GHCs.
Some packages could then opt in to explicitly support the
haskell2010
package by depending on haskell2010
or base
conditionally in their .cabal
file:
if flag(haskell2010)
build-depends: haskell2010 == 1.2.*
else
build-depends: base =>4.3 && <4.10
However, since each package would have their own haskell2010
flag,
which are not synchronised by cabal, there is no way to currently
express that haskell2010
and base
are mutually exclusive.
Proposed enhancement
Provide a new provides: <string-token>
declaration (allowed
everywhere a build-depends:
declaration is currently allowed).
The <string-token>
would denote a token in a global namespace which
at most one package selected in a given install-plan may provide.
Application for orphans scenario
This way, semigroupoids-4.3
's cabal file could be edited and have a
provides: orphans-base-foldable-either
Likewise, base-orphans
(and possibly other packages such as
base-compat
which also contain such orphans) would declare
provides: orphans-base-foldables-either
as well in their respective
.cabal
files.
Application for base
/Prelude
replacements
base.cabal
simply declares something like
provides: core-primitives
and every other package which provides a base
library which replaces
the core primitives (i.e. core typeclasses, and other entities
wired-in into the language via desugaring rules) and therefore don't
interoperate with base
would state the same provides
declaration.
Alternative constraint
-mechanism
Once could allow to declare constraint:
properties in .cabal
files.
This way, the respective base-orphans
package versions could start
declaring all package version ranges containing orphans that were
adopted into base-orphans
, e.g. base-orphans.cabal
could specify
constraint: semigroupoids >= 5
or dually, semigroupoids.cabal
(for semigroupoids-4.3 and other
versions affected) could declare
constraint: orphans-base<0
if there is no orphans-base
version compatible with
semigroupoids-4.3
, and therefore orphans-base
is incompatible.
However, constraint
is very powerful. than the proposed
provided
-mechanism. Being more general, this may be more complex to
implement in the solver. Moreover, when abused it can result in
confusing install-plan results.
Finally, when modelling the situation where more than 2 packages need
to be mutually exclusive, expressing this in the
constraint
-formulation becomes more complicated and doesn't, as each
involved package needs to know about all other conflicting
packages. Specifically the base
-replacements use-case with multiple
packages becomes impractical to express.
The proposed provided
-mechanism is weaker, makes it easy to express
mutual exclusivity among N packages, and is IMO easier to implement
as well as easier to reason about.
Alternative conflicts:
-mechanism
During the discussion a variant of constraint:
was suggested, specifically to express conflicts directly via conflicts:
which denotes the inverse of constraints:
. I.e. rather than saying
constraint: semigroupoids >= 5
constraint: orphans-base<0
one specifies
conflicts: semigroupoids < 5
conflicts: orphans-base
which is easier to reason about for the use-case of specifying conflicting package versions.
Alternative orphan-instances:
-mechanism
This would be a variant of the provides
mechanism specialised to orphan instances, by providing a machine-verifiable (i.e. tooling can help making sure the enumeration is accurate) enumeration of orphan instances provided by a package.
See #3061 (comment)
/cc @kosmikus @ezyang @dcoutts @nomeata @bergmark @23Skidoo @ttuegel @RyanGlScott