Skip to the content.

Expressing relations between types, part 5

The problem was that we couldn’t write oneOf without using unsafeCoerce. This is because the return type (kind) of OneOf is Bool.

type OneOf :: k -> [k] -> Bool
type family OneOf t ts where
    OneOf t (t ': _) = 'True
    OneOf t (_ ': ts) = OneOf t ts
    OneOf _ _ = 'False

The compiler cannot be convinced that OneOf t (_ ': ts) is 'True when OneOf t ts is 'True.

If the problem is that it returns Bool, what type should we return? We need a type from which a compiler can see where a specific type is included.

type Elem :: k -> [k] -> Type
data Elem t ts where
    Here :: Elem t (t ': ts)
    There :: Elem t ts -> Elem t (t' ': ts)

If a value is Here, it means that the type is at the beginning of the list of types. If the value is There Here, the type is the second element in the list. If There (There Here), it’s the third element, and so on.

We’ll modify the definition of Item to use Elem instead of OneOf.

type Item :: Cat -> SubCat -> Type
data Item cat subCat where
    Item :: forall cat subCat. Elem subCat (ValidSubCats cat) -> Item cat subCat

Instead of having OneOf constraint, we now need to pass a value of Elem explicitly when creating a value of Item. For example, something like this.

item1 = Item @'Cat1 @'SubCat1 Here
item2 = Item @'Cat1 @'SubCat2 (There Here)

Then, instead of oneOf, we define hElem which returns Maybe Elem. This function returns Just Here, Just (There Here), Just (There (There Here)) and so on if the specified singletone value is included in the list of singleton values, and return Nothing otherwise.

hElem :: TestEquality f => f x -> HL f xs -> Maybe (Elem x xs)
hElem x (HCons y ys) = case testEquality x y of
                           Just Refl -> Just Here
                           Nothing -> There <$> hElem x ys
hElem _ _ = Nothing

As you can see, we no longer need unsafeCoerce here.

With these changes, we can now implement buildItem.

buildItem :: SCat cat -> SSubCat subCat -> Maybe (Item cat subCat)
buildItem cat subCat = Item <$> hElem subCat (validSSubCats cat)

This buildItem has the same type as the buildItem in the previous post, so you can use it in the same way.