I'm defining a type whose type parameters have some relations. I have Item type which takes Cat and SubCat, but you can use some of the types of SubCat depending on Cat. For example, when you specify Cat1 as Cat, you can specify SubCat1 or SubCat2 as SubCat.
I implemented it using ValidSubCats type family to define valid SubCats for each Cat, and OneOf type family to define a constraint.
{-# LANGUAGE DataKinds, GADTs, PolyKinds, StandaloneKindSignatures, TypeFamilies, TypeOperators #-}
import Data.Kind (Constraint, Type)
import Data.Type.Equality ((:~:)(..), TestEquality(..))
import Unsafe.Coerce (unsafeCoerce)
data Cat = Cat1 | Cat2
type SCat :: Cat -> Type
data SCat cat where
SCat1 :: SCat 'Cat1
SCat2 :: SCat 'Cat2
data SubCat = SubCat1 | SubCat2 | SubCat3
type SSubCat :: SubCat -> Type
data SSubCat subCat where
SSubCat1 :: SSubCat 'SubCat1
SSubCat2 :: SSubCat 'SubCat2
SSubCat3 :: SSubCat 'SubCat3
type ValidSubCats :: Cat -> [SubCat]
type family ValidSubCats cat where
ValidSubCats 'Cat1 = '[ 'SubCat1, 'SubCat2 ]
ValidSubCats 'Cat2 = '[ 'SubCat2, 'SubCat3 ]
type OneOf :: k -> [k] -> Constraint
type family OneOf t ts where
OneOf t (t ': _) = ()
OneOf t (_ ': ts) = OneOf t ts
OneOf _ _ = ('True ~ 'False)
type Item :: Cat -> SubCat -> Type
data Item cat subCat where
Item :: OneOf subCat (ValidSubCats cat) => Item cat subCat
Now, I'm trying to define a function to create an Item from SCat and SSubCat.
type HL :: (k -> Type) -> [k] -> Type
data HL f t where
HCons :: f t -> HL f ts -> HL f (t ': ts)
HNil :: HL f '[]
infixr 5 `HCons`
validSSubCats :: SCat cat -> HL SSubCat (ValidSubCats cat)
validSSubCats SCat1 = SSubCat1 `HCons` SSubCat2 `HCons` HNil
validSSubCats SCat2 = SSubCat2 `HCons` SSubCat3 `HCons` HNil
instance TestEquality SSubCat where
testEquality SSubCat1 SSubCat1 = Just Refl
testEquality SSubCat2 SSubCat2 = Just Refl
testEquality SSubCat3 SSubCat3 = Just Refl
testEquality _ _ = Nothing
oneOf :: TestEquality f => f t -> HL f ts -> Maybe (OneOf t ts :~: (() :: Constraint))
oneOf x (HCons y ys) = case testEquality x y of
Just Refl -> Just Refl
Nothing -> unsafeCoerce $ oneOf x ys
oneOf _ HNil = Nothing
makeItem :: SCat cat -> SSubCat subCat -> Maybe (Item cat subCat)
makeItem sCat sSubCat = case oneOf sSubCat (validSSubCats sCat) of
Just Refl -> Just Item
Nothing -> Nothing
But as you can see, I'm using unsafeCoerce to define oneOf. I got this error when I removed it.
test.hs:54:39: error:
• Could not deduce: OneOf t ts1 ~ OneOf t (t1 : ts1)
from the context: ts ~ (t1 : ts1)
bound by a pattern with constructor:
HCons :: forall {k} (f :: k -> *) (t :: k) (ts :: [k]).
f t -> HL f ts -> HL f (t : ts),
in an equation for ‘oneOf’
at q.hs:52:10-19
Expected: Maybe (OneOf t ts :~: (() :: Constraint))
Actual: Maybe (OneOf t ts1 :~: (() :: Constraint))
NB: ‘OneOf’ is a non-injective type family
• In the expression: oneOf x ys
In a case alternative: Nothing -> oneOf x ys
In the expression:
case testEquality x y of
Just Refl -> Just Refl
Nothing -> oneOf x ys
• Relevant bindings include
ys :: HL f ts1 (bound at q.hs:52:18)
y :: f t1 (bound at q.hs:52:16)
x :: f t (bound at q.hs:52:7)
oneOf :: f t
-> HL f ts -> Maybe (OneOf t ts :~: (() :: Constraint))
(bound at q.hs:52:1)
|
54 | Nothing -> oneOf x ys
| ^^^^^^^^^^
How can I convince GHC that it can infer OneOf t (t ': ts) :~: (() :: Constraint) from OneOf t ts :~: (() :: Constraint) without using unsafeCoerce?