HList- Heterogeneous lists

Safe HaskellNone




The HList library

(C) 2004, Oleg Kiselyov, Ralf Laemmel, Keean Schupke

Type-indexed products. The public interface is described in CommonMain#TIP



The newtype for type-indexed products

newtype TIP l Source

TIPs are like Record, except element "i" of the list "l" has type Tagged e_i e_i




unTIP :: HList l


TypeIndexed Record TIP 
(HZipList (UntagR x) (UntagR y) (UntagR xy), UntagTag x, UntagTag y, UntagTag xy, SameLengths * ((:) [*] x ((:) [*] y ((:) [*] xy ([] [*])))), HTypeIndexed x, HTypeIndexed y, HUnzip TIP x y xy) => HZip TIP x y xy 
(HZipList (UntagR x) (UntagR y) (UntagR xy), UntagTag x, UntagTag y, UntagTag xy, HTypeIndexed x, HTypeIndexed y, SameLengths * ((:) [*] x ((:) [*] y ((:) [*] xy ([] [*]))))) => HUnzip TIP x y xy 
(HDeleteAtLabel k Record e v v', HTypeIndexed v') => HDeleteAtLabel k TIP e v v' 
(HUpdateAtLabel * Record e' e r r', HTypeIndexed r', (~) * e e') => HUpdateAtLabel * TIP e' e r r' 
LabelableTIPCxt x s t a b => Labelable * x TIP s t a b

make a Lens' (TIP s) a.

tipyLens provides a Lens (TIP s) (TIP t) a b, which tends to need too many type annotations to be practical

((~) * e e', HasField * e (Record l) e') => HasField * e (TIP l) e' 
(HOccurs e (TIP l1), SubType * * (TIP l1) (TIP l2)) => SubType * * (TIP l1) (TIP ((:) * e l2)) 
SubType * * (TIP l) (TIP ([] *))

Subtyping for TIPs

HasField * e (Record ((:) * x ((:) * y l))) e => HOccurs e (TIP ((:) * x ((:) * y l))) 
(~) * tee (Tagged * e e) => HOccurs e (TIP ((:) * tee ([] *)))

One occurrence and nothing is left

This variation provides an extra feature for singleton lists. That is, the result type is unified with the element in the list. Hence the explicit provision of a result type can be omitted.

(HRLabelSet ((:) * (Tagged * e e) l), HTypeIndexed l) => HExtend e (TIP l) 
Bounded (HList r) => Bounded (TIP r) 
Eq (HList a) => Eq (TIP a) 
(TypeablePolyK [*] xs, Typeable * (HList xs), Data (HList xs)) => Data (TIP xs) 
Ord (HList r) => Ord (TIP r) 
HMapOut (HComp HShow HUntag) l String => Show (TIP l) 
Ix (HList r) => Ix (TIP r) 
Monoid (HList a) => Monoid (TIP a) 
(HAppend (HList l) (HList l'), HTypeIndexed (HAppendListR * l l')) => HAppend (TIP l) (TIP l') 
Typeable ([*] -> *) TIP 
type LabelableTy TIP = LabelableLens 
type HExtendR e (TIP l) = TIP ((:) * (Tagged * e e) l) 
type HAppendR * (TIP l) (TIP l') = TIP (HAppendListR * l l') 

Type-indexed type sequences

class (HAllTaggedEq l, HRLabelSet l) => HTypeIndexed l Source

this constraint ensures that a TIP created by mkTIP has no duplicates


class HAllTaggedEq l Source


HAllTaggedEq ([] *) 
(HAllTaggedEq l, (~) * tee (Tagged k e e')) => HAllTaggedEq ((:) * tee l) 

Shielding type-indexed operations

The absence of signatures is deliberate! They all must be inferred.

onRecord :: (HAllTaggedEq l, HLabelSet [*] (LabelsOf l), HAllTaggedLV l) => (Record r -> Record l) -> TIP r -> TIP l Source

tipyUpdate :: (HUpdateAtLabel * record v v r r, SameLength' * * r r) => v -> record r -> record r Source

tipyProject :: (HAllTaggedEq l, HLabelSet [*] (LabelsOf l), H2ProjectByLabels ls r l b, HAllTaggedLV l) => proxy ls -> TIP r -> TIP l Source

Use Labels to specify the first argument

tipyLens' :: (HAllTaggedEq t, HLabelSet [*] (LabelsOf t), HasField * b (Record t) b, HUpdateAtLabel2 * b b t t, SameLength' * * t t, SameLabels [*] [*] t t, HAllTaggedLV t, Functor f) => (b -> f b) -> TIP t -> f (TIP t) Source

provides a Lens' (TIP s) a. hLens' :: Label a -> Lens' (TIP s) a is another option.

tipyLens :: (HAllTaggedEq t, HAllTaggedEq s, HLabelSet [*] (LabelsOf t), HLabelSet [*] (LabelsOf s), HasField * b (Record t) b, HasField * b (Record s) b, HUpdateAtLabel2 * b b t s, HUpdateAtLabel2 * b b s t, SameLength' * * t s, SameLength' * * s t, SameLabels [*] [*] s t, HAllTaggedLV t, HAllTaggedLV s, Functor f) => (b -> f b) -> TIP s -> f (TIP t) Source

provides a Lens (TIP s) (TIP t) a b

When using set (also known as .~), tipyLens' can address the ambiguity as to which field "a" should actually be updated.

tipyProject2 :: (HAllTaggedEq l1, HAllTaggedEq l, HLabelSet [*] (LabelsOf l), HLabelSet [*] (LabelsOf l1), H2ProjectByLabels ls r l l1, HAllTaggedLV l1, HAllTaggedLV l) => proxy ls -> TIP r -> (TIP l, TIP l1) Source

The same as tipyProject, except also return the types not requested in the proxy argument

conversion to and from HList

class SameLength a ta => TagUntagFD a ta | a -> ta, ta -> a where Source

TagR can also be used to avoid redundancy when defining types for TIC and TIP.

 type XShort = TagR [A,B,C,D]
 type XLong = [Tagged A A, Tagged B B, Tagged C C, Tagged D D]

an equivalent FD version, which is slightly better with respect to simplifying types containing type variables (in ghc-7.8 and 7.6): http://stackoverflow.com/questions/24110410/

With ghc-7.10 (http://ghc.haskell.org/trac/ghc/ticket/10009) the FD version is superior to the TF version:

class (UntagR (TagR a) ~ a) => TagUntag a where
    type TagR a :: [*]
    hTagSelf :: HList a -> HList (TagR a)
    hUntagSelf :: HList (TagR a) -> HList a

instance TagUntag '[] where
    type TagR '[] = '[]
    hTagSelf _ = HNil
    hUntagSelf _ = HNil

instance TagUntag xs => TagUntag (x ': xs) where
    type TagR (x ': xs) = Tagged x x ': TagR xs
    hTagSelf (HCons x xs) = Tagged x HCons hTagSelf xs
    hUntagSelf (HCons (Tagged x) xs) = x HCons hUntagSelf xs

type family UntagR (xs :: [*]) :: [*]
type instance UntagR '[] = '[]
type instance UntagR (x ': xs) = Untag1 x ': UntagR xs

Length information should flow backwards

>>> let len2 x = x `asTypeOf` (undefined :: HList '[a,b])
>>> let f = len2 $ hTagSelf (hReplicate Proxy ())
>>> :t f
f :: HList '[Tagged () (), Tagged () ()]


hTagSelf :: HList a -> HList ta Source

hUntagSelf :: HList ta -> HList a Source


TagUntagFD ([] *) ([] *) 
(TagUntagFD xs ys, (~) * txx (Tagged * x x)) => TagUntagFD ((:) * x xs) ((:) * txx ys) 

type TagUntag xs = TagUntagFD xs (TagR xs) Source

type UntagTag xs = TagUntagFD (UntagR xs) xs Source

Sometimes the type variables available have TagR already applied (ie the lists have elements like Tagged X X). Then this abbreviation is useful:

type family TagR a :: [*] Source


type TagR ([] *) = [] * 
type TagR ((:) * x xs) = (:) * (Tagged * x x) (TagR xs) 

type family UntagR ta :: [*] Source


type UntagR ([] *) = [] * 
type UntagR ((:) * (Tagged * y y) ys) = (:) * y (UntagR ys) 

type family Untag1 x :: * Source


type Untag1 (Tagged k k1 x) = x 

tipHList :: (TagUntagFD a1 l, TagUntagFD a ta, Profunctor p, Functor f) => p (HList a) (f (HList a1)) -> p (TIP ta) (f (TIP l)) Source

Iso (TIP (TagR a)) (TIP (TagR b)) (HList a) (HList b)

tipHList' :: (TagUntagFD a ta, Profunctor p, Functor f) => p (HList a) (f (HList a)) -> p (TIP ta) (f (TIP ta)) Source

Iso' (TIP (TagR s)) (HList a)

conversion to and from Record

tipRecord :: (Profunctor p, Functor f) => p (Record r) (f (Record l)) -> p (TIP r) (f (TIP l)) Source

Iso (TIP s) (TIP t) (Record s) (Record t)

typeIndexed may be more appropriate

tipRecord' :: (Profunctor p, Functor f) => p (Record r) (f (Record r)) -> p (TIP r) (f (TIP r)) Source

Iso' (TIP (TagR s)) (Record a)


hZipTIP :: (TagUntagFD y ta1, TagUntagFD x ta, TagUntagFD a l, HZipList x y a) => TIP ta -> TIP ta1 -> TIP l Source

specialization of hZip

hUnzipTIP :: (HAllTaggedEq l2, HAllTaggedEq l1, TagUntagFD a1 l2, TagUntagFD a l1, TagUntagFD l ta, HLabelSet [*] (LabelsOf l1), HLabelSet [*] (LabelsOf l2), HZipList a a1 l, HAllTaggedLV l2, HAllTaggedLV l1) => TIP ta -> (TIP l1, TIP l2) Source

specialization of hUnzip

TIP Transform

class TransTIP op db where Source

Transforming a TIP: applying to a TIP a (polyvariadic) function that takes arguments from a TIP and updates the TIP with the result.

In more detail: we have a typed-indexed collection TIP and we would like to apply a transformation function to it, whose argument types and the result type are all in the TIP. The function should locate its arguments based on their types, and update the TIP with the result. The function may have any number of arguments, including zero; the order of arguments should not matter.

The problem was posed by Andrew U. Frank on Haskell-Cafe, Sep 10, 2009. http://www.haskell.org/pipermail/haskell-cafe/2009-September/066217.html The problem is an interesting variation of the keyword argument problem.

Examples can be found in examples/TIPTransform.hs and examples/TIPTransformM.hs


ttip :: op -> TIP db -> TIP db Source


(HMember * (Tagged * op op) db b, Arity op n, TransTIP1 b n op db) => TransTIP op db 

class TransTIP1 b n op db where Source


ttip1 :: Proxy b -> Proxy n -> op -> TIP db -> TIP db Source


Fail ((,,) * Symbol [*]) ((,,) * Symbol [*] (TypeNotFound * notfun) "in TIP" db) => TransTIP1 False HZero notfun db 
HTPupdateAtLabel * TIP op op db => TransTIP1 True n op db 
(HMember * (Tagged * arg arg) db b, TransTIP2 b arg op db) => TransTIP1 False (HSucc n) (arg -> op) db 

class TransTIP2 b arg op db where Source


ttip2 :: Proxy b -> (arg -> op) -> TIP db -> TIP db Source


Fail ((,,) * Symbol [*]) ((,,) * Symbol [*] (TypeNotFound * arg) "in TIP" db) => TransTIP2 False arg op db 
(HOccurs arg (TIP db), TransTIP op db) => TransTIP2 True arg op db 

Monadic version

class Monad m => TransTIPM m op db where Source

In March 2010, Andrew Frank extended the problem for monadic operations. This is the monadic version of TIPTransform.hs in the present directory.

This is the TF implementation. When specifying the operation to perform over a TIP, we can leave it polymorphic over the monad. The type checker will instantiate the monad based on the context.


ttipM :: op -> TIP db -> m (TIP db) Source


(Monad m, HMember * (Tagged * op op) db b, Arity (m' op) n, TransTIPM1 b n m (m' op) db) => TransTIPM m (m' op) db 

class Monad m => TransTIPM1 b n m op db where Source


ttipM1 :: Proxy b -> Proxy n -> op -> TIP db -> m (TIP db) Source


(Fail ((,,) * Symbol [*]) ((,,) * Symbol [*] (TypeNotFound * op) "in TIP" db), Monad m) => TransTIPM1 False HZero m op db 
(Monad m, (~) (* -> *) m m', HTPupdateAtLabel * TIP op op db) => TransTIPM1 True n m (m' op) db 
(Monad m, HMember * (Tagged * arg arg) db b, TransTIPM2 b m arg op db) => TransTIPM1 False (HSucc n) m (arg -> op) db 

class TransTIPM2 b m arg op db where Source


ttipM2 :: Proxy b -> (arg -> op) -> TIP db -> m (TIP db) Source


Fail ((,,) * Symbol [*]) ((,,) * Symbol [*] (TypeNotFound * op) "in TIP" db) => TransTIPM2 False m arg op db 
(HOccurs arg (TIP db), TransTIPM m op db) => TransTIPM2 True m arg op db 

Sample code

>>> import Data.HList.TypeEqO
>>> import Data.HList.FakePrelude
>>> import Data.HList.HOccurs
>>> :{
newtype Key    = Key Integer deriving (Show,Eq,Ord)
newtype Name   = Name String deriving (Show,Eq)
data Breed     = Cow | Sheep deriving (Show,Eq)
newtype Price  = Price Float deriving (Show,Eq,Ord)
data Disease   = BSE | FM deriving (Show,Eq)
type Animal =  TagR '[Key,Name,Breed,Price]
>>> :{
let myTipyCow :: TIP Animal -- optional
    myTipyCow = Key 42 .*.  Name "Angus" .*.  Cow .*.  Price 75.5 .*. emptyTIP
    animalKey :: (HOccurs Key l, SubType l (TIP Animal)) => l -> Key
    animalKey = hOccurs
Session log
>>> :t myTipyCow
myTipyCow :: TIP Animal
>>> hOccurs myTipyCow :: Breed
>>> BSE .*. myTipyCow
TIPH[BSE,Key 42,Name "Angus",Cow,Price 75.5]
>>> Sheep .*. hDeleteAtLabel (Label::Label Breed) myTipyCow
TIPH[Sheep,Key 42,Name "Angus",Price 75.5]
>>> tipyUpdate Sheep myTipyCow
TIPH[Key 42,Name "Angus",Sheep,Price 75.5]
>>> tipyProject2 (Proxy :: Labels '[Name,Price]) myTipyCow
(TIPH[Name "Angus",Price 75.5],TIPH[Key 42,Cow])
>>> tipyProject (Proxy :: Labels '[Name,Price]) myTipyCow
TIPH[Name "Angus",Price 75.5]

Don't bother repeating the type error:

>>> Sheep .*. myTipyCow
...No instance for (Fail (DuplicatedLabel (Label Breed)))