7
votes

I've got a "linear" type family, i.e. of the form

type family Foo a
type instance Foo T1 = T2
type instance Foo T2 = T3
...
type instance Foo T9 = T10

In my particualr use-case, it is very convenient to define the "reverse" family FooRev and then enforce the constraint (FooRev (Foo x) ~ x):

type family FooRev a
type instance FooRev T10 = T9
type instance FooRev T9 = T8
...
type instance FooRev T2 = T1

The reverse family allows GHC to infer many types that would otherwise be ambiguous due to non-injectivity. It is essentially the same idea that was proposed here. This solution works pretty well, but it is annoying, programatic, and error-prone to have to define a "reverse" type family by listing out all the cases. Is there a more generic way to define the reverse of a linear family such as Foo?

2
Maybe you could use data families instead somehow, which are known injective. Not sure if it fits your problem. - luqui
Metaprogramming (i.e. Template Haskell) might generate both, keeping your code DRY if that's your concern. Otherwise, you could define a type-level association list, a type level lookup, a type level map swap, and derive both type families from the list. - chi
@luqui I just saw that before posting, but as far as I know, that's not really what I need. In my case, the Ti are all type Bar a b c where the type parameters change. That won't fit into a data family, will it? - crockeea
@chi Perhaps you could demonstrate one of those ideas? - crockeea

2 Answers

5
votes

I think FunctionalDependencies should be the best solution for your case:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Foo a b | a -> b, b -> a
instance Foo T1 T2
instance Foo T2 T3
...

Now each b can be inferred from an a and vice versa.

5
votes

I decided to give @chi's idea try, and but I came up with something a little simpler in the end.

{-# LANGUAGE TypeOperators, DataKinds, TypeFamilies #-}

type family NextElt (a :: *) (xs :: [*]) where
  NextElt a (a ': b ': cs) = b
  NextElt a (b ': c ': cs) = NextElt a (c ': cs)

type family PrevElt (a :: *) (xs :: [*]) :: * where
  PrevElt a (b ': a ': xs) = b
  PrevElt a (b ': c ': xs) = PrevElt a (c ': xs)

data T1
data T2
data T3
type TSeq = '[T1, T2, T3]

type NextRp a = NextElt a TSeq
type PrevRp a = PrevElt a TSeq

Using a type list to represent the linear sequence of types allows me to express the relationship without writing each type twice (which is necessary using type family instances or instances of a class). The type families above uses a sliding-window approach to search for an element in a list with a previous or next element. These type families are generic (and can be extended to work for non-* kinds using Data.Type.Equality.)