DataKinds and Type Instances

The following example is its own version of my real problem. It seems like it is somehow similar to Extracting information from the limited existential DataKinds types , but I couldn't get the answers I was looking for.

Suppose we have a final advanced DataKind K with types A and B and a multi-type Proxy data type for generating terms with type types *.

 {-# LANGUAGE DataKinds, PolyKinds, GADTs, FlexibleInstances, FlexibleContexts #-} data K = A | B data Proxy :: k -> * where Proxy :: Proxy k 

Now I want to write Show -instances for each type of Proxy a , where A has the form K , which is exactly two:

 instance Show (Proxy A) where show Proxy = "A" instance Show (Proxy B) where show Proxy = "B" 

But to use Show -instance, I have to explicitly provide a context, even if the type is limited to K :

 test :: Show (Proxy a) => Proxy (a :: K) -> String test p = show p 

My goal is to get rid of type-type constraints. This may seem inconsequential, but in my real application this has serious consequences.

I could also define one, but more general Show tool like this:

 instance Show (Proxy (a :: K)) where show p = "?" 

This actually allows me to abandon the restriction, but a new problem is to distinguish between two types A and B

So, is there a way to eat my cake and have it too? That is, it is not necessary to provide a type-type constraint in the type test (view annotations are beautiful) and still have two different implementations of Show (for example, by differentiating types in some way)?

In fact, it would also be nice to abandon the entire type class if I could just associate the corresponding types ( A , B ) with my specific values ​​(here: "A" , "B" ) in a context where I just have information about type. I don’t even know how to do this.

I would be very grateful for the ideas provided.

+6
source share
3 answers

No, It is Immpossible. In a context where you have “type information only”, you really have no information at runtime. Type information is erased. Therefore, even for closed views, where in principle it can be shown that, given this type, you can always find a dictionary, you still need a class constraint. A class constraint ensures that at compile time, when the GHC knows the type, it can select the appropriate instance to pass. At run time, the information that enters it is lost, and there is no way to do the same. Writing an instance of "one size fits all" really works, because then the exact type no longer matters for selection.

I don’t know the full picture, but you can get around this by explicitly associating the dictionary dictionary or string with the value that you go around ...

+6
source

You can add another class.

 class Kish (k :: K) where toK :: proxy k -> K instance Kish A where toK _ = A instance Kish B where toK _ = B instance Kish k => Show (Proxy k) where showsPrec n _ = case toK (Proxy :: Proxy k) of A -> ... B -> ... 

Now you will continue to stick with the context, but this is more general, which may be useful for other things.

If it turns out that you usually need to distinguish between proxies a lot, then you should switch to GADT, which you can simply check as needed, instead of using a proxy.

+5
source

Knowing that:

  • a has the form K
  • the only types of type K are a and B
  • Show (Proxy A) contains
  • Show (Proxy B) contains

enough to prove that Show (Proxy A) satisfied. But a type class is not just a proposition that we need to prove for use with our type, it also provides an implementation. In order to actually use show (x :: Proxy a) , we need not only to prove that there is an implementation for Show (Proxy A) , we really need to know which one is there.

Variables of the Haskell type (without restrictions) are parametric: there is no way to be completely polymorphic in a , and also to be able to test a to provide different behavior for a and B The “different behavior” you want is roughly “close to parametric,” since you may not be parametric, as it is just choosing a different instance for each type, when you know that there is one for each type. But this is still something that violates the contract, which means test :: forall (a :: K). Proxy a -> String test :: forall (a :: K). Proxy a -> String .

Class type restrictions allow your code to be nonparametric in variables with a limited type, since you can use class type mapping from types to implementations to switch the behavior of your code to the type that it refers to. So test :: forall (a :: K). Show (Proxy a) => Proxy a -> String test :: forall (a :: K). Show (Proxy a) => Proxy a -> String works. (In terms of the actual implementation, the same end-user who selects type a also provides an instance of Show (Proxy A) for the code generated by your function in use.)

You can use the idea of instance Show (Proxy (a :: K)) ; now your function, which is parametric in type a :: K , can still use show (x :: Proxy a) , because there is one instance that works for all a :: K But the instance itself is facing the same problem; the show implementation in the instance is parametric in a , and therefore cannot check it anyway to return another type-based string.

You can use something like dfeuer's answer, where Kish uses the variable parameter type with a limited type to basically let you check the type at runtime; the instance dictionary passed to satisfy the Kish a constraint is basically a run-time record for which the type was chosen for the variable a (in a circular fashion). Tapping this idea will complement you to Typeable . But you still need some kind of restriction to make your code nonparametric in a .

You can also use a type that explicitly represents the timing of a or B (unlike Proxy ), which is an empty value at runtime and provides only a representation of the compilation time of the choice), something like:

 {-# LANGUAGE DataKinds, GADTs, KindSignatures, StandaloneDeriving #-} data K = A | B data KProxy (a :: K) where KA :: KProxy A KB :: KProxy B deriving instance Show (KProxy a) test :: KProxy a -> String test = show 

(Please note that I can not only implement Show (Kproxy a) , but also get it, although this requires standalone output)

This is the use of GADT KProxy to allow test be nonparametric in a , essentially doing the same job as the Kish constraint from dfeuer's answer, but avoiding the need to add an extra constraint to sign your type. In an earlier version of this post, I stated that test was able to do this while remaining “fair” parametric in a , which was incorrect.

Of course, now, in order to transfer the proxy server, you must write KA or KB . Don’t worry where you had to write Proxy :: Proxy A to actually choose the type (which is often the case with proxies, given that you usually use them only to fix the type, which would otherwise be ambiguous). But where it would be unambiguous, the compiler will catch you if you do not agree with the rest of the call, but you cannot write only one character, such as Proxy , and the compiler will output the correct value. You can refer to this class of types:

 class KProxyable (a :: K) where kproxy :: KProxy a instance KProxyable A where kproxy = KA instance KProxyable B where kproxy = KB 

Then you can use KA instead of Proxy :: Proxy A and KProxy instead of having the compiler KProxy bare Proxy type. Stupid approximate time:

 foo :: KProxy a -> KProxy a -> String foo kx ky = show kx ++ " " ++ show ky 

GHCI:

 λ foo KA kproxy "KA KA" 

Note. I do not need to have a KProxyable restriction anywhere; I use KProxy at the point where the type is known. It should still “come in from above,” though (just like an instance dictionary that satisfies your Show (Proxy A) constraint); it is not possible to have a function parametric in type a :: K , to independently use the corresponding KProxy a .

Since this is a correspondence between the constructors and the type that does this work, I do not believe that you can make a general proxy in this style the way you can with an empty Proxy runtime. However, TemplateHaskell can generate these types of proxies for you; I think the singleton concept is a general idea here, and therefore https://hackage.haskell.org/package/singletons probably provides what you need, but I'm not very familiar with how to really use this package.

+2
source

All Articles