haskell / haskell/cabal

Dependency solver should take type class instances into consideration

Open
#3,280 0 comments 0 reactions 0 assignees View on GitHub
cabal-install: other cabal-install: solver type: enhancement
Dominant language
Haskell
Stars
1.7k
Forks
750
Avg merge
4d 3h
Merged PRs (30d)
28

Description

A major problem with the PVP is that packages cannot usefully levy constraints on the type class instances which they want in scope; the dependency solver could easily pick versions of transitive dependencies which expose conflicting instances. The problem is clear: type classes are a _global_ concern, which are what the dependency solver is all about, but there is no easy way to get this information to the solver.

There have been a few proposals to let the solver know about this. For example, the provides mechanism in #3061 would be a way for users to manually specify what instances are required, when this information is needed for dependency solving. But this ticket is about a more radical design: we want Cabal to _literally_ know exactly ALL of the orphan instances a package defines. Here is how we do it:
1. Define a new metadata format which can be included in Cabal files, describing the list of orphan instances defined by this package. For example, if a package defines an orphan instance `instance C T`, then we record the metadata `instance pkg-defining-c:ModuleC.C pkg-defining-t:ModuleT.T`. The details:
- We define a "generalized original name", which is similar to the original names used in GHC but without committing to a version of the package or any of its dependencies. For example, if the original name of Map would be `containers-0.4-ABCD:Data.Map.Types.Map`, the generalized original name is `containers:Data.Map.Types.Map` (we dropped `0.4` and the version hash.) (Note for Backpack: this generalized original name WOULD include instantiations.)
- Every orphan instance (as defined by GHC) is recorded by recording the generalized original name of the class in question, as well as the generalized original name of the HEAD of every type variable. (For example, an instance `instance Show a => Show (Maybe a)` would simply record `base:Prelude.Show` and `base:Prelude.Maybe`, dropping the constraints and type structure.) (For GHC aficionados, this is the "rough match" heuristic.)
- The set of orphan instances can be conditionalized using the same condition language that `cabal` files support.
2. Obviously, we don't expect users to write this manually; thus, GHC is enhanced with a mode to output this information from the interface files (similar to the `--abi-hash` flag). This information may then be copypasted into the Cabal file. Note that GHC has no way of figuring out what is going on with conditionals/preprocessors, so those must be manually written in by the user. Cabal uses this generation process to check the consistency of this information with a build from GHC.
3. These instances can then be considered to be "provided" in the same sense of #3061; except that top-level type variables in the instance are considered to conflict with everything.

Open problems:
1. Specifying the generalized original name can be difficult in some situations; for example, suppose we get `T` in scope by some module which reexports it from a different module. If we pick a different version of the package for that module, it could be reexported from somewhere else, which would affect the original name. So ideally, we would track the USAGE which provided the type (GHC does track this!) and during the course of dependency solving, Cabal would determine how these reexports are resolved. In fact, this is a problem which affects PVP with the dependency solver already, since Haskell level reexports can be a source of PVP non-compliance.
2. I don't know how to implement conflicts in the dependencies with wildcards. Consider the one arity case, `C a` with `C T` and `C T2`, but `C T` and `C T2` do not conflict with each other.
3. For this to work with old versions of GHC, Cabal would have to ship with a GHC API program per version of GHC to support to extract this information.

CC @hvr

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.