Is it okay to use the cabal solver to conditionally define dependencies and instances like this?
Let’s say I have a package alice, and a datatype that can be an instance of a class in package bob, but otherwise alice does not depend on bob
The common approach is to define a library alice-bob-instance and define an orphan instance inside this package.
If people need the bob instance for alice they can just import alice-bob-instance.
The problem is, yes, this is an orphan instance, so wherever one wants to use the instance you’ll have to explicitly import the module it comes from which is a bit annoying.
But let’s say I did something different. I actually release two versions of alice-bob-instance. A version 0, which was a completely empty package with no dependencies. And a version 0.1 which actually has the implementation of the alice-bob instance, but not the instance declaration itself.
Now anyone needing the instance will actually need to import alice-bob-instance > 0, but that’s not a big deal.
Then, inside alice.cabal, I do the following:
Flag alice-bob-instance-flag {
Description: Enable Bob instance
Default: False
Manual: False
}
if flag(alice-bob-instance-flag)
build-depends: alice-bob-instance > 0
else
build-depends: alice-bob-instance = 0
Then I can use CPP to conditionally import the alice-bob-instance module and also to implement the instance (which will just be a call to the code in that module).
It seems like this just avoids the orphan instances, whilst ensuring that instances (and hence dependencies) are only pulled in if they’re explicitly needed. Also whilst this could be done with manual flags, if a package that depends on alice and bob needs the instance whilst it can’t set a flag, but it can set an dependency on alice-bob-instance > 0.
Now arguably this is a little naughty as a flag is changing the external interface of the module (i.e. we’re exporting a new instance) but surely lots of automatic flags do this, as they seem to be designed to allow compatibility with a larger set of dependent library versions. Unless a library is completely opaquely wrapping the types in that dependent library something is going to leak into the interface anyway.
Is this a pattern that’s okay to use or is it a terrible idea?
Discussion in the ATmosphere