General class parameters

Let's say I have a class:

class C abt where f :: (a, b) -> (ta, tb) 

Now with this definition, I can define instances, for example:

 (a,b) -> (Maybe a, Maybe b) (a,b) -> ([a], [b]) 

But not for (as I understand it):

 (a,b) -> (a,b) (a,b) -> ((a, a), (b, b)) 

I could instead change the class definition as follows:

 type family T abx class C ab where f :: (a, b) -> (T aba, T abb) 

What could I do above, but then I could only declare one f for each a and b .

Basically, I want to be able to pass a type family as t in the original definition, which is implicitly allowed by type checking, if known. I don’t want to just do this f :: (a, b) -> (c, d) , since I want to preserve the invariant that the tags a and b do the same with them, so swap . f swap . f is the same type as f . swap f . swap . I think I might need families of injective types (from GHC 8.0), but I'm not sure. But maybe there is another way?

+6
source share
1 answer

This may or may not answer your question depending on what you really need to do.

The standard solution is to use newtype to define new instances. For instance.

 newtype Pair a = Pair (a, a) instance C ab Pair where f = ... 

However, this will cause f to be of type

 f :: (a, b) -> (Pair a, Pair b) 

instead of the desired

 f :: (a, b) -> ((a, a), (b, b)) 

The latter can be restored in a boring way:

 f' :: (a, b) -> ((a, a), (b, b)) f' p = case fp of (Pair x, Pair y) -> (x, y) 

Now writing functions of an β€œadapter”, such as f' , looks redundant: after all, we just remove the newtype shell, which does not change the presentation of the runtime. Worse, it can be inefficient: consider the conversion of [Pair a] to [(a, a)] . To do this, we will need to display the entire list, so we pay O (n), in fact, do nothing.

An effective alternative can be built using safe constraints :

 import Data.Coerce f'' :: forall a b. (a, b) -> ((a, a), (b, b)) f'' = coerce (f :: (a, b) -> (Pair a, Pair b)) 

This allows you to use newtype to select an instance of the instance, but at the same time remove them when they interfere.

Now, if your goal is to define new instances without newtype s, this helps a little. If instead you just want to remove the newtype wrappers from the instance methods, this may help.

+5
source

All Articles