@Cirdec's already accepted answer is a great pragmatic answer for this use case. I wanted to write a little about the more general methods (especially Data.Constraint.Forall mentioned in the comment, since it almost works for this use case, but not quite, and there is another thing from constraints that really works), but I also wanted to try explain a little about why it is not so possible at first, and what Show1 , Forall and Lifting do to get around this (they are different different compromises). So he got a little time, apologized.
By standard Haskell mechanisms you cannot have such a βwildcardβ restriction. The reason is that Show creates a type constraint, with a potentially different instance (with different method definitions) for each type to which you could apply it.
When your code needs an instance of Show (CDeq qa) and finds instance (Show a, Show ????) => Show (CDeq qa) , it means that now it also needs to find an instance for Show a and Show ???? .
Show a easy; either a already selected as some concrete type of type Int , and it can use an instance of Show for that type (or an error if it does not exist in the scope). Or your code was in a function that was polymorphic in a ; in this case, there should be a restriction of Show a on the function you wrote, so the compiler will simply rely on the caller who has selected a specific a and is passed in the definition of the Show a instance.
But restriction of substitution Show ???? is different. We are not talking about a specific type here, so the path to resolution will not work. And we are not even talking about a polymorphic restriction in the sense that there is a variable of the type that the caller would choose (in this case, we could solve the problem of choosing one instance of the dictionary for the caller).
You need to show qa and q (qa) , and q (q (qa) , etc. But each of them can have its own instance! Types disappear at runtime, so the compiler cannot even try to round all these instances (or require the caller to go through an unlimited number of instances) and emit a code that switches between what Show calls. It must emit a code that calls only one version of Show ; either a specific instance that it could select, or one that is passed to the caller side of the function.
One way around this is with an alternate type class such as Show1 . Here the "overload unit" is in the constructor of type * -> * . It is not possible to create an instance of Show1 for types such as Int (they are not the correct form), but one instance of Show1 q applicable to all types of qa , so you do not need an unlimited number of instances to support qa , q (qa) , q (q (qa)) , etc. more.
But there are also instances of Show that are polymorphic in some type variables. For example, an instance of the list Show a => Show [a] ; knowing that this instance exists (and there are no overlapping instances), we know that we will use the same instance for [a] , [[a]] , [[[a]]] , etc. . If we could somehow write the required restriction such a polymorphic instance.
There is no direct way to say that we need a polymorphic instance β the constraint language allows us to query instances for specific types or for specific types that the caller selects. But the Data.Forall module in the restrictions package ( https://hackage.haskell.org/package/constraints ), which @Cirdec suggests in the commentary, uses smart tricks inside to provide several ways to do this. 1 Here is an example of how it would look:
{-
The limitation of ForallF Show q is forall a. Show (qa) forall a. Show (qa) . 2 But this is not a limitation directly, so we cannot just extract Show using this; we need to write the instance manually so that we can massage.
A ForallF gives us access to instF , which is of type forall pf a. ForallF pf :- p (fa) forall pf a. ForallF pf :- p (fa) . :- - type of package restrictions; values ββof type c :- d are proof that when the restriction c contains the restriction d , it also holds (or in terms of the dictionary words of the instance: it contains the dictionary for d , which is parameterized in the dictionary for c ), So, ForallF pf :- p (fa) is proof that with ForallF pf we can get p (fa) . Typical application syntax is a less detailed way of binding types in which we use instF , we want the left side :- bind to ForallF Show q , which, as we know, we have from the instance constraint. This means that the right side will give us Show (qa) as we needed! The \\ operator simply takes the expression on the left and c :- d on the right and basically connects the instances of c and d for us; the expression will be evaluated with access to the dictionary for d , but for the general expression only c is required.
Here is a usage example:
Ξ» Deeper 'a' (Deeper None (Deeper None Stop)) 'a' None None Stop it :: Nested None Char
Hooray! But why did I use None ? What happens when we try it with nested lists?
Ξ» :t Deeper [] (Deeper [] Stop) Deeper [] (Deeper [] Stop) :: Nested [] [t] Ξ» Deeper [] (Deeper [] Stop) <interactive>:65:1: error: β’ No instance for (Show (Data.Constraint.Forall.Skolem (Data.Constraint.Forall.ComposeC Show []))) arising from a use of 'print' β’ In a stmt of an interactive GHCi command: print it
get out. Something went wrong? Well, our polymorphic instance of Show for lists is actually Show a => Show [a] . The head of an instance is polymorphic, so it applies to all types of forms [a] . But this also requires an additional constraint, which Show a is satisfied, so it is not truly polymorphic. Basically, what happens is that the internal unsolved thing in Data.Constraint does not have an instance for Show (it cannot have instances for the method to work), so we get the error above. And that is really good; the dictionaries for [a] contain a nested dictionary for a , so the trick of getting the instance we know is polymorphic and then unsafe. Copying it to the desired type is not applicable here. ForallF only works for finding instances that are completely polymorphic, without any restrictions.
But there is one more thing that the package of restrictions can offer here! Data.Constraint.Lifting gives us the Lifting pf class, which represents the idea of ββ" p (fa) when pa is executed." The idea that the restriction p "raises" a constructor of type f . Actually, this is exactly what you need, since you can simply apply it recursively to embed arbitrarily many depths q .
{-
Here, the Lifting method of the Lifting class does basically what instF did instF . lifting :: Lifting pf => pa :- p (fa) , so when we have Lifting Show q and we have Show a , then we can use \\ and Lifting (used in the correct type) to get the Show (qa) dictionary Show (qa) , which we need for a recursive call to Show .
Now we can show Nested for list types:
Ξ» Deeper [] (Deeper [[True]] Stop) [] [[True]] Stop it :: Nested [] [Bool]
Data.Constraint.Lifting has many predefined instances for foreplay things, but you will most likely have to write your own instances. Fortunately, this is pretty much related to writing:
instance Lifting SomeClass MyType where lifting = Sub Dict
An instance replicator does all the actual work for you if your type really allows this class to "go up" through it.
1 My understanding of the code in this module is not 100% complete (and all the details are a bit related to making it as safe as possible), but basically the method is to apply the class to a hidden inexplicable thing and capture the dictionary. Since no third-party instance could really reference our raw thing, the only way the instance can be resolved is if it was actually polymorphic and worked for anything. Thus, a captured dictionary can be unsafeCoerced applicable to any type you like.
2 There are several other Forall* options for representing the various "forms" of polymorphism in the restriction. I believe that you cannot make a version of the same size because you do not need to specify a variable in which you are polymorphic, which means that you cannot use the applicable restriction, you must have something that requires the class as a parameter, so is all non-polymorphic parameters and applies them together in a certain way.