Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern Matching on Promoted Types

We can write single, comprehensive instances for parameters of kind *:

class MyClass d where
    f :: d -> Int

instance MyClass (Maybe d) where  
    f _ = 3

test1 :: Maybe d -> Int
test1 x = f x

This compiles just fine, and note that we do NOT need a (MyClass (Maybe d)) constraint on test1 because the compiler finds a matching instance for any Maybe d.

We can even write an all-encompassing instance for class parameters that are type constructors:

class MyClass2 (a :: * -> *) where
    g :: Tagged (a Int) Int

instance MyClass2 a where
    g = Tagged 3

test2 :: Int
test2 = untag (g :: Tagged (Maybe Int) Int)

This also compiles fine, and test2 does not need a (MyClass2 Maybe) constraint, because the compiler finds a matching instance.

Consider the following toy code to compute the length of a (promoted) type list:

{-# LANGUAGE TypeOperators,
DataKinds,
KindSignatures,
GADTs,
FlexibleInstances,
FlexibleContexts,
ScopedTypeVariables #-}

import Data.Tagged
import Data.Proxy

data HList :: [*] -> * where
              HNil :: HList '[]
              HCons :: a -> HList as -> HList (a ': as)

class ListLen a where
    len :: Tagged a Int

instance ListLen (HList '[]) where
    len = Tagged 0

instance (ListLen (HList as)) => ListLen (HList (a ': as)) where
    len = Tagged (1+(untag (len :: Tagged (HList as) Int)))

test3 :: Int
test3 = untag (len :: Tagged (HList '[Int, Double, Integer]) Int)

test4 :: (Proxy (HList qs)) -> Int
test4 (_ :: Proxy (HList qs)) = untag (len :: Tagged (HList qs) Int) -- error occurs here

This results in the error:

No instance for (ListLen (HList qs))
      arising from a use of `len'
    Possible fix: add an instance declaration for (ListLen (HList qs))
    ...

If we comment out the signature for test4, GHCi infers the type as

test4 :: (ListLen (HList qs)) => (Proxy (HList qs)) -> Int

but this shouldn't be necessary. Clearly, I've written instances for ListLen that match any conceivable type list: an 'empty list' case, and a 'cons' case. The problem remains the same if I change the kind of the ListLen parameter to have kind [*] (i.e., remove the HList wrapper in ListLen and its instances).

If we comment out test4, test3 compiles and runs fine: since I (essentially) gave it explicit syntax ('[Int,Double,Integer]) for how I constructed the type list, the compiler was able to find matching instances.

I'm attempting to write code that builds a type list for me, so I will not have to write out the explicit type list syntax. However, it seems that using explicit syntax is the only way GHC is able to match these comprehensive instances. Perhaps there's a case I'm missing? Syntax I'm not using?

What can I do to make GHC realize that I have an instance for everything of kind [*]? I'm using GHC 7.4.2. This might be related to a previous post about deconstructing non-promoted types. THE paper on type promotion can be found here.

like image 574
crockeea Avatar asked Nov 03 '22 04:11

crockeea


1 Answers

This is not exactly what you want, but gets pretty close. I started out with

data Proxy a where
  Proxy :: ListLen a => Proxy

then

data Proxy a where
  Proxy :: Tagged a Int -> Proxy a

resulting in

test4 :: (Proxy (HList qs)) -> Int
test4 (Proxy len) = untag len

The problem is you either need to have the constraint, or have Proxy contain evidence of the required class (even though instances exist for all available kinds). Here, the class method is just included in Proxy.

A completely different option is to just not use a type class at all, and implement len in the usual way (transcribed from Data.List.length).

len :: HList a -> Int
len l = len' l 0
  where
    len' :: HList a -> Int -> Int
    len' HNil a = a
    len' (HCons _ xs) a = len' xs $! a + 1
like image 100
ScootyPuff Avatar answered Nov 15 '22 08:11

ScootyPuff