What I roughly want is this:
data A = ...
data B = ...
data C = ...
class HasA t where
getA :: t -> A
class HasB t where
getB :: t -> B
class HasC t where
getC :: t -> C
So I can do something like this (pseudocode follows):
a :: A
b :: B
x = mkRecord { elemA a, elemB b }
y = mkRecord { elemB b, elemA a }
-- type of `x` == type of `y`
Naturally, only the appropriate get functions work, in the above case getA and getB.
I'd also like the following functions
slice :: Subset a b => a -> b
slice x = -- just remove the bits of x that aren't in type b.
add :: e -> a -> a ++ e
add e x = -- add an element to the "record" (compile error if it's already there)
I feel like this is not a new problem so perhaps a resolution for this already exists. Note that I don't require the solution to be extensible, the amount of types I need to deal with is finite and known, but of course and extensible one wouldn't hurt.
I've found a couple of packages that seem to be in the field of what I'm looking for, namely HList and extensible (perhaps extensible is better because I want my records unordered). I got a bit lost in the Hackage docs so I'd like just some sample code (or a link to some sample code) that roughly achieves what I'm looking for.
This is exactly what HList is good for. However, since I don't have the right setup to test something with the HList package right now (and besides, it has more confusing data definitions), here is a minimal example of HList that uses singletons for the type-level list stuff.
{-# LANGUAGE DataKinds, TypeOperators, GADTs,TypeFamilies, UndecidableInstances,
PolyKinds, FlexibleInstances, MultiParamTypeClasses
#-}
import Data.Singletons
import Data.Promotion.Prelude.List
data HList (l :: [*]) where
HNil :: HList '[]
HCons :: x -> HList xs -> HList (x ': xs)
The add function is the simplest: it is just HCons:
add :: x -> HList xs -> HList (x ': xs)
add = HCons
Something more interesting is combining two records:
-- Notice we are using `:++` from singletons
combine :: HList xs -> HList ys -> HList (xs :++ ys)
combine HNil xs = xs
combine (x `HCons` xs) ys = x `HCons` (xs `combine` ys)
Now, for your get function, you need to dispatch based on the type-level list. To do this, you need an overlapping type class.
class Has x xs where
get :: xs -> x
instance {-# OVERLAPS #-} Has x (HList (x ': xs)) where
get (x `HCons` _) = x
instance Has x (HList xs) => Has x (HList (y ': xs)) where
get (_ `HCons` xs) = get xs
Finally, we can use Has to define a similar Subset class. Same idea as before.
class Subset ys xs where
slice :: xs -> ys
instance Subset (HList '[]) (HList xs) where
slice _ = HNil
instance (Get y (HList xs), Subset (HList ys) (HList xs)) =>
Subset (HList (y ': ys)) (HList xs) where
slice xs = get xs `HCons` slice xs
As you mention in parens, the simple HList form does not ensure you have only one of any type of field (so get just returns the first field, ignoring the rest). If you want uniqueness, you can just add a constraint to the HList constructor.
data Record (l :: [*]) where
Nil :: Record '[]
Cons :: (NotElem x xs ~ 'True) => x -> Record xs -> Record (x ': xs)
However, defining Subset using Record looks like it involves some proofs. :)
Related
Consider following definition of a HList:
infixr 5 :>
data HList (types :: [*]) where
HNil :: HList '[]
(:>) :: a -> HList l -> HList (a:l)
And a type family Map to map over typelevel lists:
type family Map (f :: * -> *) (xs :: [*]) where
Map f '[] = '[]
Map f (x ': xs) = (f x) ': xs
Now I would like to define sequence equivalence for HLists. My attempt looks like
hSequence :: Applicative m => HList (Map m ins) -> m (HList ins)
hSequence HNil = pure HNil
hSequence (x :> rest) = (:>) <$> x <*> hSequence rest
But I get errors like this:
Could not deduce: ins ~ '[]
from the context: Map m ins ~ '[]
bound by a pattern with constructor: HNil :: HList '[]
For me it looks like the compiler isn't sure that if Map m returns [] on some list then the list is empty. Sadly, I don't see any way to convince it to that fact. What should I do in this situation?
I am using GHC 8.6.5 with following extensions:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
First, there's an error here:
type family Map (f :: * -> *) (xs :: [*]) where
Map f '[] = '[]
Map f (x ': xs) = (f x) ': Map f xs
--^^^^^-- we need this
After that is fixed, the issue here is that we need to proceed by induction on ins, not on Map f ins. To achieve that, we need a singleton type:
data SList :: [*] -> * where
SNil :: SList '[]
SCons :: SList zs -> SList ( z ': zs )
and then an additional argument:
hSequence :: Applicative m => SList ins -> HList (Map m ins) -> m (HList ins)
hSequence SNil HNil = pure HNil
hSequence (SCons ins') (x :> rest) = (:>) <$> x <*> hSequence ins' rest
This now compiles. Matching on SNil / SCons refines ins to either '[] or z ': zs, so Map m ins can be unfolded one step as well. This allows us to make the recursive call.
As usual, we can remove the additional singleton argument through a suitable typeclass. I'm reasonably sure that some of this can be automated exploiting the singletons library.
class SingList ins where
singList :: SList ins
instance SingList '[] where
singList = SNil
instance SingList zs => SingList (z ': zs) where
singList = SCons singList
hSequence2 :: (Applicative m, SingList ins)
=> HList (Map m ins) -> m (HList ins)
hSequence2 = hSequence singList
This GADT preserves the spine ("length") of type level lists past type erasure:
data Spine (xs :: [k]) :: Type where
NilSpine :: Spine '[]
ConsSpine :: Spine xs -> Spine (x : xs)
From this, we can prove these lemmas:
mapNil' :: forall f xs. Map f xs ~ '[] => Spine xs -> xs :~: '[]
mapNil' NilSpine = Refl
type family Head (xs :: [k]) :: k where Head (x : _) = x
type family Tail (xs :: [k]) :: [k] where Tail (_ : xs) = xs
data MapCons f y ys xs =
forall x xs'. (xs ~ (x : xs'), y ~ f x, ys ~ Map f xs') => MapCons
mapCons' :: forall f xs y ys. Map f xs ~ (y : ys) => Spine xs -> MapCons f y ys xs
mapCons' (ConsSpine _) = MapCons
Now, Spine is a singleton family: Spine xs has exactly one value for each xs. We can therefore erase it.
mapNil :: forall f xs. Map f xs ~ '[] => xs :~: '[]
mapNil = unsafeCoerce Refl -- safe because mapNil' exists
mapCons :: forall f xs y ys. Map f xs ~ (y : ys) => MapCons f y ys xs
mapCons = unsafeCoerce MapCons -- safe because mapCons' exists
These lemmas can then be used to define your function:
hSequence :: forall m ins. Applicative m => HList (Map m ins) -> m (HList ins)
hSequence HNil | Refl <- mapNil #m #ins = pure HNil
hSequence (x :> rest) | MapCons <- mapCons #m #ins = (:>) <$> x <*> hSequence rest
By starting with Spine, we can build a justification for why our logic works. Then, we can erase all the singleton junk we don't need at runtime. This is an extension of how we use types to build a justification for why our programs work, and then we erase them for the runtime. It's important to write mapNil' and mapCons' so we know what we're doing works.
HList is quite an unwieldy type. I recommend using something like this one instead, which is similar to one from vinyl.
{-# language PolyKinds, DataKinds, GADTs, ScopedTypeVariables, RankNTypes, TypeOperators #-}
import Data.Kind
import Control.Applicative
infixr 4 :>
-- Type is the modern spelling of the * kind
data Rec :: [k] -> (k -> Type) -> Type
where
Nil :: Rec '[] f
(:>) :: f a -> Rec as f -> Rec (a ': as) f
htraverse
:: forall (xs :: [k]) (f :: k -> Type) (g :: k -> Type) m.
Applicative m
=> (forall t. f t -> m (g t))
-> Rec xs f -> m (Rec xs g)
htraverse _f Nil = pure Nil
htraverse f (x :> xs) =
liftA2 (:>) (f x) (htraverse f xs)
If you like, you can define
hsequence
:: forall (xs :: [k]) (g :: k -> Type) m.
Applicative m
=> Rec xs (Compose m g) -> m (Rec xs g)
hsequence = htraverse getCompose
Note that
HList xs ~= Rec xs Identity
I have two heterogeneous list structures. The first HList is a normal heterogeneous list, the second Representation is a heterogeneous list where all the members are sets.
{-# Language KindSignatures, DataKinds, TypeOperators, TypeFamilies, GADTs, FlexibleInstances, FlexibleContexts #-}
import Data.Kind
import Data.Set
data Representation (a :: [Type]) where
NewRep :: Representation '[]
AddAttribute :: (Ord a, Eq a) => Set a -> Representation b -> Representation (a ': b)
(%>) :: (Ord a, Eq a) => [a] -> Representation b -> Representation (a ': b)
(%>) = AddAttribute . fromList
infixr 6 %>
-- | A HList is a heterogenenous list.
data HList (a :: [Type]) where
HEmpty :: HList '[]
(:>) :: a -> HList b -> HList (a ': b)
infixr 6 :>
(I've made these instances of Show at the bottom if that is helpful.)
Now I have a bunch of functions that work on HLists but don't work on Representations. I could rewrite all the functions but that's a big pain. I'd rather if there was some way to make Representations in HLists and back. That way I can use all the relevant functions without having to redefine them. So I started to do this. It was pretty easy to make a function that goes from Representations to HLists:
type family Map (f :: Type -> Type) (xs :: [Type]) :: [Type] where
Map f '[] = '[]
Map f (a ': b) = f a ': Map f b
-- | soften takes an attribute representation and converts it to a heterogeneous list.
soften :: Representation a -> HList (Map Set a)
soften NewRep = HEmpty
soften (AddAttribute a b) = a :> soften b
However the other way is quite a bit harder. I tried the following:
-- | rigify takes a heterogeneous list and converts it to a representation
rigify :: HList (Map Set a) -> Representation a
rigify HEmpty = NewRep
rigify (a :> b) = AddAttribute a $ rigify b
However this fails, the compiler is not able to deduce that a ~ '[] in the first line. And fails in a similar fashion on the second.
It appears to me that the compiler can't reason backwards in the same way it can forward. This is not really very surprising, but I don't know exactly what the issue is, so I'm not really very sure how to get the compiler to reason correctly. My thought was to make a type family that is the reverse of Map like so:
type family UnMap (f :: Type -> Type) (xs :: [Type]) :: [Type] where
UnMap f '[] = '[]
UnMap f ((f a) ': b) = a ': UnMap f b
and then rewrite rigify in terms of UnMap instead of Map:
-- | rigify takes a heterogeneous list and converts it to a representation
rigify :: HList a -> Representation (UnMap Set a)
rigify HEmpty = NewRep
rigify (a :> b) = AddAttribute a $ rigify b
This seems to reduce the problem but it still doesn't compile. This time we are having the issue that a in the second line cannot be shown to be of type Set x which is required for AddAttribute. This makes total sense to me but I don't know how I could remedy the issue.
How can I convert from a heterogeneous list to a Representation?
Show instances:
instance Show (HList '[]) where
show HEmpty = "HEmpty"
instance Show a => Show (HList '[a]) where
show (a :> HEmpty) = "(" ++ show a ++ " :> HEmpty)"
instance (Show a, Show (HList (b ': c))) => Show (HList (a ': b ': c)) where
show (a :> b) = "(" ++ show a ++ " :> " ++ tail (show b)
instance Show (Representation '[]) where
show NewRep = "NewRep"
instance Show a => Show (Representation '[a]) where
show (AddAttribute h NewRep) = '(' : show (toList h) ++ " %> NewRep)"
instance (Show a, Show (Representation (b ': c))) => Show (Representation (a ': b ': c)) where
show (AddAttribute h t) = '(' : show (toList h) ++ " %> " ++ tail (show t)
HList is usually wrong. What I mean is that as soon as you try to do very much, you're likely to end up with lots of problems. You can solve the problems, but it's annoying and often inefficient. There's another, very similar, construction that can go a lot further before it falls down.
data Rec :: [k] -> (k -> Type) -> Type where
Nil :: Rec '[] f
(:::) :: f x -> Rec xs f -> Rec (x ': xs) f
type f ~> g = forall x. f x -> g x
mapRec :: (f ~> g) -> Rec xs f -> Rec xs g
mapRec _ Nil = Nil
mapRec f (x ::: xs) = f x ::: mapRec f xs
Note that you can do a certain sort of mapping without bringing in any type families at all!
Now you can define
data OSet a = Ord a => OSet (Set a)
newtype Representation as = Representation (Rec as OSet)
An awful lot of generic HList functions can be rewritten very easily to support Rec instead.
You can write bidirectional pattern synonyms to simulate your current interface if you like.
Ord a makes Eq a redundant: Ord a implies Eq a because class Eq a => Ord a.
data Representation (a :: [Type]) where
...
AddAttribute :: Ord a => Set a -> Representation b -> Representation (a ': b)
(%>) :: Ord a => [a] -> Representation b -> Representation (a ': b)
You can't write rigify with quite this type: soften throws away the Ord-ness stored at each AddAttribute. You can use
data OSet a where OSet :: Ord a => Set a -> OSet a
soften :: Representation xs -> HList (Map OSet xs)
rigify :: HList (Map OSet xs) -> Representation xs
and you may apply the age old "list of pairs is a pair of lists" trick on top of that
type family AllCon (xs :: [Constraint]) :: Constraint where
AllCon '[] = ()
AllCon (x : xs) = (x, AllCon xs)
data Dict c = c => Dict
soften :: Representation xs -> (HList (Map Set xs), Dict (AllCon (Map Ord xs)))
rigify :: AllCon (Map Ord xs) => HList (Map Set xs) -> Representation xs
though I shall go with the former because it is more concise.
Use unsafeCoerce. The alternative is to reify some type information with a GADT and write a proof. While that is good practice, that requires you to drag around (potentially large) values that represent things that are simply true, so you'll end up using unsafeCoerce anyway to avoid them. You can skip the proofs and go to the end products directly.
-- note how I always wrap the unsafeCoerce with a type signature
-- this means that I reduce the chance of introducing something actually bogus
-- I use these functions instead of raw unsafeCoerce in rigify, because I trust
-- these to be correct more than I trust unsafeCoerce.
mapNil :: forall f xs. Map f xs :~: '[] -> xs :~: '[]
mapNil Refl = unsafeCoerce Refl
data IsCons xs where IsCons :: IsCons (x : xs)
mapCons :: forall f xs. IsCons (Map f xs) -> IsCons xs
mapCons IsCons = unsafeCoerce IsCons
rigify :: HList (Map OSet xs) -> Representation xs
rigify HEmpty = case mapNil #OSet #xs Refl of Refl -> NewRep
rigify (x :> xs) = case mapCons #OSet #xs IsCons of
IsCons -> case x of OSet x' -> AddAttribute x' (rigify xs)
A proper proof would go as follows:
data Spine :: [k] -> Type where
SpineN :: Spine '[]
SpineC :: Spine xs -> Spine (x : xs)
mapNil' :: forall f xs. Spine xs -> Map f xs :~: '[] -> xs :~: '[]
mapNil' SpineN Refl = Refl
mapNil' (SpineC _) impossible = case impossible of {}
mapCons' :: forall f xs. Spine xs -> IsCons (Map f xs) -> IsCons xs
mapCons' SpineN impossible = case impossible of {}
mapCons' (SpineC _) IsCons = IsCons
For every list xs, there is one and only one (fully defined) value of Spine xs (it is a singleton type). To get from real proofs (like mapNil') to their convenience versions (like mapNil), remove all the singleton arguments and make sure the return type is a mere proposition. (A mere proposition is a type with 0 or 1 values.) Replace the body with one that deeply evaluates the remaining arguments and uses unsafeCoerce for the return value.
Use a type class
The desired behavior for rigify can be obtained by using a multi paramater type class instead.
class Rigible (xs :: [Type]) (ys :: [Type]) | xs -> ys where
rigify :: HList xs -> Representation ys
instance Rigible '[] '[] where
rigify HEmpty = NewRep
instance (Ord h, Rigible t t') => Rigible (Set h ': t) (h ': t') where
rigify (a :> b) = AddAttribute a $ rigify b
Here we use a multiparam type class Rigible with an attached function rigify. Our two parameters are the type for the representation and the type for the heterogeneous list. They are functionally dependent to avoid ambiguity.
In this way only HLists that are composed entirely of sets are Rigible. From here you can even add the definition of soften to Rigible as well.
-- | soften takes a representation and converts it to a heterogeneous list.
-- | rigify takes a heterogeneous list and converts it to a representation.
class Rigible (xs :: [Type]) (ys :: [Type]) | xs -> ys where
rigify :: HList xs -> Representation ys
soften :: Representation ys -> HList xs
instance Rigible '[] '[] where
rigify HEmpty = NewRep
soften NewRep = HEmpty
instance (Ord h, Rigible t t') => Rigible (Set h ': t) (h ': t') where
rigify (a :> b) = AddAttribute a $ rigify b
soften (AddAttribute a b) = a :> soften b
This requires the additional pragma
{-# Language MultiParamTypeClasses, FunctionalDependencies, UndecidableInstances #-}
I'm trying to write two functions to extract a value from an HList, but I can't seem to make GHC happy.
The first function would have signature extract :: HList a -> [b] which extracts all the elements of type b from the list. I only succeeded in writing it by asking the types in a to have Typeable instances.
class OfType a b where
oftype :: a -> [Maybe b]
instance OfType (HList '[]) b where
oftype = const []
instance (Typeable t, Typeable b, OfType (HList ts) b) => OfType (HList (t ': ts)) b where
oftype (x :- xs) = (cast x :: Maybe b) : oftype xs
extract :: OfType a b => a -> [b]
extract = catMaybes . oftype
Which is suboptimal, as one doesn't really need the Typeable constraint to write any instance of extract.
I tried to use type equalities and inequalities in constraints, but this only gave me overlapping instances.
The second function I'm trying to write would have signature extract' :: Contains h n => HList h -> n which extracts the first element of type n in the list, and the context says that the list actually contains one element of that type.
Is it possible to write extract without Typeable constraints?
Is it possible to write extract' without Typeable constraints?
How can one write Contains?
Since you want to check for type equality at compile time, I believe overlapping instances are unavoidable (and I'm not a fan of those...).
Also, I'm not 100% sure I got the overlapping pragmas right.
{-# LANGUAGE DataKinds, TypeOperators, ScopedTypeVariables,
MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-}
{-# OPTIONS -Wall #-}
module HListFilter where
import Data.HList.HList
class OfType a b where
oftype :: a -> [b]
instance OfType (HList '[]) b where
oftype = const []
instance {-# OVERLAPS #-} (OfType (HList ts) t) => OfType (HList (t ': ts)) t where
oftype (HCons x xs) = x : oftype xs
instance {-# OVERLAPPABLE #-} (OfType (HList ts) b) => OfType (HList (t ': ts)) b where
oftype (HCons _ xs) = oftype xs
test :: HList '[Int, Char, [Char], Char, Bool]
test = HCons (1::Int) (HCons 'a' (HCons "foo" (HCons 'b' (HCons True HNil))))
test_result :: [Char]
test_result = oftype test -- "ab"
András Kovács referred to a type family approach. This is one way to do it:
type family Equal (x :: *) (y :: *) where
Equal x x = 'True
Equal x y = 'False
type family Check (b :: *) (as :: [*]) :: [Bool] where
Check b '[] = '[]
Check b (a ': as) = (b `Equal` a) ': Check b as
class ps ~ Check b as =>
OfType (ps :: [Bool]) (as :: [*]) b where
extract :: HList as -> [b]
The ps ~ Check b as superclass context is critical here, as a matter of timing. GHC always commits to an instance before checking the instance constraints, but it doesn't even try to find an instance until after solving the superclass constraints. So we need to use the superclass constraint to fix which instances to select.
instance OfType '[] '[] b where
extract HNil = []
instance (OfType ps as b, a ~ b) =>
OfType ('True ': ps) (a ': as) b where
extract (HCons x xs) = x : extract xs
instance (OfType ps as b, Equal b a ~ 'False) =>
OfType ('False ': ps) (a ': as) b where
extract (HCons _ xs) = extract xs
Once you've done this, you can actually write an interface that avoids the "extra" class parameter:
class OfType' (as :: [*]) (b :: *) where
extract' :: HList as -> [b]
instance OfType ps as b => OfType' as b where
extract' = extract
It's quite easy to write Contains and extract'. However, writing good instances of Contains necessitates exactly the same sort of hoop jumping as OfType. The class you'd like to have is this:
class Contains xs y where
contains :: y `Elem` xs
-- Elem is part of the dependently typed folklore.
data Elem y xs where
Here :: Elem y (y ': xs)
There :: Elem y xs -> Elem y (x ': xs)
But writing instances will again force you into overlapping or closed type families. I know I've written this code somewhere around SO, but you should probably be able to work out the overlapping version pretty easily; the type family version will follow the same pattern as OfType, generally.
I'm playing around with the constraints package (for GHC Haskell). I have a type family for determining if a type-level list contains an element:
type family HasElem (x :: k) (xs :: [k]) where
HasElem x '[] = False
HasElem x (x ': xs) = True
HasElem x (y ': xs) = HasElem x xs
This works, but one thing it doesn't give me is the knowledge that
HasElem x xs entails HasElem x (y ': xs)
since the type family isn't an inductive definition of the "is element of" statement (like you would have in agda). I'm pretty sure that, until GADTs are promotable to the type level, there is no way to express list membership with a data type.
So, I've used the constraints package to write this:
containerEntailsLarger :: Proxy x -> Proxy xs -> Proxy b -> (HasElem x xs ~ True) :- (HasElem x (b ': xs) ~ True)
containerEntailsLarger _ _ _ = unsafeCoerceConstraint
Spooky, but it works. I can pattern match on the entailment to get what I need. What I'm wondering is if it can ever cause a program to crash. It seems like it couldn't, since unsafeCoerceConstraint is defined as:
unsafeCoerceConstraint = unsafeCoerce refl
And in GHC, the type level is elided at runtime. I thought I'd check though, just to make sure that doing this is ok.
--- EDIT ---
Since no one has given an explanation yet, I thought I would expand the question a little. In the unsafe entailment I'm creating, I only expect a type family. If I did something that involved typeclass dictionaries instead like this:
badEntailment :: Proxy a -> (Show a) :- (Ord a)
badEntailment _ = unsafeCoerceConstraint
I assume that this would almost certainly be capable of causing a segfault. Is this true? and if so, what makes it different from the original?
--- EDIT 2 ---
I just wanted to provide a little background for why I am interested in this. One of my interests is making a usable encoding of relational algebra in Haskell. I think that no matter how you define functions to work on type-level lists, there will be obvious things that aren't proved correctly. For example, a constraint (for semijoin) that I've had before looked like this (this is from memory, so it might not be exact):
semijoin :: ( GetOverlap as bs ~ Overlap inAs inBoth inBs
, HasElem x as, HasElem x (inAs ++ inBoth ++ inBs)) => ...
So, it should be obvious (to a person) that if I take union of two sets, that it contains an element x that was in as, but I'm not sure that it's possible the legitimately convince the constraint solver of this. So, that's my motivation for doing this trick. I create entailments to cheat the constraint solver, but I don't know if it's actually safe.
I don't know if this will suit your other needs, but it accomplishes this particular purpose. I'm not too good with type families myself, so it's not clear to me what your type family can actually be used for.
{-# LANGUAGE ...., UndecidableInstances #-}
type family Or (x :: Bool) (y :: Bool) :: Bool where
Or 'True x = 'True
Or x 'True = 'True
Or x y = 'False
type family Is (x :: k) (y :: k) where
Is x x = 'True
Is x y = 'False
type family HasElem (x :: k) (xs :: [k]) :: Bool where
HasElem x '[] = 'False
HasElem x (y ': z) = Or (Is x y) (HasElem x z)
containerEntailsLarger :: proxy1 x -> proxy2 xs -> proxy3 b ->
(HasElem x xs ~ 'True) :- (HasElem x (b ': xs) ~ 'True)
containerEntailsLarger _p1 _p2 _p3 = Sub Dict
An approach using GADTs
I've been having trouble letting go of this problem. Here's a way to use a GADT to get good evidence while using type families and classes to get a good interface.
-- Lots of extensions; I don't think I use ScopedTypeVariables,
-- but I include it as a matter of principle to avoid getting
-- confused.
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, UndecidableInstances #-}
{-# LANGUAGE TypeFamilies, TypeOperators #-}
{-# LANGUAGE DataKinds, PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE GADTs #-}
-- Some natural numbers
data Nat = Z | S Nat deriving (Eq, Ord, Show)
-- Evidence that a type is in a list of types
data ElemG :: k -> [k] -> * where
Here :: ElemG x (x ': xs)
There :: ElemG x xs -> ElemG x (y ': xs)
deriving instance Show (ElemG x xs)
-- Take `ElemG` to the class level.
class ElemGC (x :: k) (xs :: [k]) where
elemG :: proxy1 x -> proxy2 xs -> ElemG x xs
-- There doesn't seem to be a way to instantiate ElemGC
-- directly without overlap, but we can do it via another class.
instance ElemGC' n x xs => ElemGC x xs where
elemG = elemG'
type family First (x :: k) (xs :: [k]) :: Nat where
First x (x ': xs) = 'Z
First x (y ': ys) = 'S (First x ys)
class First x xs ~ n => ElemGC' (n :: Nat) (x :: k) (xs :: [k]) where
elemG' :: proxy1 x -> proxy2 xs -> ElemG x xs
instance ElemGC' 'Z x (x ': xs) where
elemG' _p1 _p2 = Here
instance (ElemGC' n x ys, First x (y ': ys) ~ 'S n) => ElemGC' ('S n) x (y ': ys) where
elemG' p1 _p2 = There (elemG' p1 Proxy)
This actually seems to work, at least in simple cases:
*Hello> elemG (Proxy :: Proxy Int) (Proxy :: Proxy '[Int, Char])
Here
*Hello> elemG (Proxy :: Proxy Int) (Proxy :: Proxy '[Char, Int, Int])
There Here
*Hello> elemG (Proxy :: Proxy Int) (Proxy :: Proxy '[Char, Integer, Int])
There (There Here)
This doesn't support the precise entailment you desire, but I believe the ElemGC' recursive case is probably the closest you can get with such an informative constraint, at least in GHC 7.10.
Suppose we have a following definition of HList:
data HL spec where
HLNil :: HL ()
HLCons :: h -> HL t -> HL (h, t)
Is it possible to somehow enforce a shared constraint on its items?
As an example, following is my attempt to constrain the items to have Show instances, which fails with Couldn't match type `Char' with `Int':
class HLSpecEach spec item
instance HLSpecEach () item
instance (HLSpecEach t item, h ~ item) => HLSpecEach (h, t) item
a :: (Show item, HLSpecEach spec item) => HL spec -> Int
a = undefined
b :: HL (Int, (Char, ()))
b = undefined
c = a b
Easy to do if you have constraint kinds and type families. First, let me say I prefer using DataKinds for clarity
data HList ls where
HNil :: HList '[]
HCons :: x -> HList xs -> HList (x ': xs)
type family ConstrainAll (c :: * -> Constraint) (ls :: [*]) :: Constraint
type instance ConstrainAll c '[] = ()
type instance ConstrainAll c (x ': xs) = (c x, ConstrainAll c xs)
showAll :: ConstrainAll Show xs => HList xs -> [String]
showAll HNil = []
showAll (HCons x xs) = (show x) : showAll xs
if you don't use the new extensions it is possible, but much uglier. One option is to define custom classes for everything
class ShowAll ls where
showAll :: HList ls -> [Show]
instance ShowAll () where
showAll _ = []
instance (ShowAll xs, Show x) => ShowAll (x,xs)
showAll (HCons x xs) = (show x) : (showAll xs)
which I find ugly. A more clever approach would be to fake constraint kinds
class Constrained tag aType where
isConstained :: tag aType
data HListT tag ls where
HNilT :: HListT tag ()
HConsT :: x -> tag x -> HListT tag xs -> HListT tag (x,xs)
data Proxy (f :: * -> *) = Proxy
class ConstainedAll tag ls where
tagThem :: Proxy tag -> HList ls -> HListT tag ls
instance ConstainedAll tag () where
tagThem _ _ = HNilT
instance (ConstainedAll tag xs, Constrained tag x) => ConstainedAll tag (x,xs) where
tagThem p (HCons x xs) = HConsT x isConstained (tagThem p xs)
which you can then use like
data Showable x where Showable :: Show x => Showable x
instance Show x => Constrained Showable x where isConstained = Showable
--inferred type showAll' :: HListT Showable xs -> [String]
showAll' HNilT = []
showAll' (HConsT x Showable xs) = (show x) : showAll' xs
--inferred type: showAll :: ConstainedAll Showable xs => HList xs -> [String]
showAll xs = showAll' (tagThem (Proxy :: Proxy Showable) xs)
example = showAll (HCons "hello" (HCons () HNil))
which should (havent tested) work with any GHC with GADTs, MPTC, Flexible Contexts/Instances, and Kind Signatures (you can get rid of the last one easily).
EDIT: In GHC 7.6+ you should use
type family ConstrainAll (c :: k -> Constraint) (ls :: [k]) :: Constraint
(k instead of the *) and turn on PolyKinds, but this won't work with the GHC 7.4 implementation of PolyKinds (hence the monomorphic code). In the same way, defining
data HList f ls where
HNil :: HList f '[]
HCons :: !(f x) -> !(HList f xs) -> HList f (x ': xs)
lets you avoid code duplication when you want things like a lazy vs strict HLists or when you want a list of dictionaries, or universal variants of higher kinded types, etc.