Unfortunately, there is no really clean solution to this problem.
The biggest drawback of your current approach is the presence of any similar "automatic" instance of Show anywhere in the program, both of them will stop working completely, and you will get a "duplicate instances" error message.
The main problem is that instance resolution works first, first matching with the right side of the instance declaration. If there is a match, the machine agrees to accept this expression, and then goes and tries to resolve everything that is required by the left side.
So, in your case, finding an instance for Show Foo for any Foo will unconditionally match your instance, and then permission will not be executed if it does not find an instance of SappState Foo .
OverlappingInstances mitigates this a bit since it will first look for more specific instances. This way, Show Int will be resolved using a regular instance, since it mentions Int . But if there was something like instance SappState2 a => Show a , then any Foo without a more specific instance will result in a duplicate instance error.
I would recommend that SappState artists record the Show instance manually. You can reduce costs by providing the showSappState :: SappState a => a -> String utility function so that their instances can be simply
instance Show Foo where show = showSappState
[There are several more methods in the correct implementation of Show , but the same general approach applies)
If you want to be sure that all SappState instances SappState also Show instances, you can use a superclass to provide this:
class Show a => SappState a where ...
In fact, there are several suggestions that allow you to automatically implement superclasses that will perfectly solve your problem, but so far nothing has been implemented in GHC. One example is IntrinsicSuperclasses .
For completeness, it is worth mentioning that UndecidableInstances somewhat dangerous, because this can lead to the fact that the resolution of the instance does not stop. The rules that guarantee that it will end are necessarily somewhat conservative, since the problem in Turing is over, and in some cases, like this, they need to be disabled.
The problem with instance SappState a => Show a is that it reduces the Show a limit to the SappState a limit, and there is no obvious "progress" since the new search that is being searched is the same size as the old one. Imagine what could happen if someone also wrote instance Show a => SappState a .