Skip to content

Proposal for provides-mechanism #3061

Open
@hvr

Description

@hvr

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 orphan instance Foldable (Either a) (for base<4.8)
  • Starting with base-4.8, base started providing instance Foldable (Either a)
  • Then base-orphans-0.1 started equally providing an such an orphan instance like semigroupoids-4.3
  • semigroupoids-5.0 now depends on base-orphans

However, there's currently no way to express that the combination

  • base >= 4.8
  • semigroupoids < 5, and
  • base-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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions