Search code examples
haskelltypesdependent-type

How to properly wrap over a datatype indexed by an inductive datatype?


I'm trying to thinly wrap around a singleton version of a list. I have trouble deconstructing it. Here's a minimal implementation:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ExplicitForAll #-}

module InductiveWrapper where

import Data.Kind (Type)
import Data.Proxy (Proxy)

import GHC.Prim (coerce)

data List a = Nil | Cons a (List a)

data SList :: [ k ] -> Type where
  SNil  ::                        SList '[]
  SCons :: Proxy k -> SList ks -> SList (k ': ks)

newtype Set a = S [ a ]

data SSet :: Set k -> Type where
  SS :: SList xs -> SSet ('S xs)

type family Add (el :: k) (set :: Set k) :: Set k where
  Add el ('S xs) = 'S (el ': xs)

uncons :: forall k (el :: k) (set :: Set k)
        . SSet (Add el set) -> (Proxy el, SSet set)
uncons (SS (x `SCons` xs)) = (x, SS xs)

Here's the relevant bit of the error:

Could not deduce: set ~ 'S ks
      from the context: Add el set ~ 'S xs
        bound by a pattern with constructor:
                   SS :: forall k (xs :: [k]). SList xs -> SSet ('S xs),
                 in an equation for ‘uncons’
[...]
 or from: xs ~ (k1 : ks)
        bound by a pattern with constructor:
                   SCons :: forall k1 (k2 :: k1) (ks :: [k1]).
                            Proxy k2 -> SList ks -> SList (k2 : ks),
[...]
• Relevant bindings include
        xs :: SList ks (bound at InductiveWrapper.hs:37:29)
        x :: Proxy k1 (bound at InductiveWrapper.hs:37:19)
        xs' :: SList xs (bound at InductiveWrapper.hs:37:14)
        s :: SSet (Add el set) (bound at InductiveWrapper.hs:37:8)

The problem as I understand is that Add el set gets stuck because the type checker doesn't understand that the only way set could be constructed is by using 'S.

How do I unstuck it or resolve this problem by other means? Apart from using type instead of newtype. The whole reason I'm doing this is to completely hide the use of [ k ] and SList.


Solution

  • Type families are non-injective, which technically means that you can't go from result to arguments, right to left. Except not. GHC 8.0 introduced TypeFamilyDependencies, which lets you specify injectivity for type families, like this:

    type family Add (el :: k) (set :: Set k) = (set' :: Set k) | set' -> el set where
      Add el ('S xs) = 'S (el ': xs)
    

    However, for some reason that I don't yet completely understand, this still doesn't work in your case, causing the same issue. I suspect it may have something to do with the fact that the list in question is double wrapped, not sure.

    But I do have another workaround: you can ditch the whole injectivity issue and specify your type family the other way around - from the list to the tuple. Except you'd need two type families - one for head and one for tail:

    type family Head set where Head ('S (el ': xs)) = el
    type family Tail set where Tail ('S (el ': xs)) = 'S xs
    
    uncons :: SSet set -> (Proxy (Head set), SSet (Tail set))
    uncons (SS (x `SCons` xs)) = (x, SS xs)
    

    But this seems a bit overengineered to me. If you just need to uncons these type sets, I would go with a good ol' type class, which has the unbeatable advantage of wrapping types and values together, so you don't have to jump through hoops to match them manually:

    class Uncons set res | set -> res  where
        uncons :: SSet set -> res
    
    instance Uncons ('S (el ': xs)) (Proxy el, SSet ('S xs)) where
        uncons (SS (x `SCons` xs)) = (x, SS xs)