Safe Haskell | None |
---|
The HList library
(C) 2004, Oleg Kiselyov, Ralf Laemmel, Keean Schupke
Some very basic technology for faking dependent types in Haskell.
- class Apply f a where
- class ApplyAB f a b where
- applyAB :: f -> a -> b
- data Fun cxt getb = Fun (forall a. FunCxt cxt a => a -> FunApp getb a)
- data Fun' cxt geta = Fun' (forall b. FunCxt cxt b => FunApp geta b -> b)
- type family FunApp fns a
- type family FunCxt cxts a :: Constraint
- data HPrint = HPrint
- data HRead = HRead
- data HShow = HShow
- data HComp g f = HComp g f
- data Comp = Comp
- newtype HSeq x = HSeq x
- data HFlip = HFlip
- data Label l = Label
- labelToProxy :: Label l -> Proxy l
- class ShowLabel l where
- hTrue :: Proxy True
- hFalse :: Proxy False
- type family HAnd t1 t2 :: Bool
- hAnd :: Proxy t1 -> Proxy t2 -> Proxy (HAnd t1 t2)
- type family HOr t1 t2 :: Bool
- hOr :: Proxy t1 -> Proxy t2 -> Proxy (HOr t1 t2)
- class HCond t x y z | t x y -> z where
- type family HBoolEQ t1 t2 :: Bool
- data HNat
- hZero :: Proxy HZero
- hSucc :: Proxy (n :: HNat) -> Proxy (HSucc n)
- hPred :: Proxy (HSucc n) -> Proxy n
- class HNat2Integral n where
- hNat2Integral :: Integral i => Proxy n -> i
- type family HNatEq t1 t2 :: Bool
- type family HLt x y :: Bool
- hLt :: Proxy x -> Proxy y -> Proxy (HLt x y)
- data HNothing = HNothing
- newtype HJust x = HJust x
- class HEq x y b | x y -> b
- hEq :: HEq x y b => x -> y -> Proxy b
- class Fail x
- module Data.Proxy
A heterogeneous apply operator
simpler/weaker version where type information only propagates forward
with this one. app
defined below, is more complicated / verbose to define,
but it offers better type inference. Most uses have been converted to
app
, so there is not much that can be done with Apply
.
HLookupByHNat n l => Apply (FHLookupByHNat l) (Proxy HNat n) | |
Apply (FHUProj sel ns) (HList l, Proxy HNat (HSucc n)) => Apply (Proxy Bool False, FHUProj sel ns) (HList (: * e l), Proxy HNat n) | |
Apply (Proxy Bool True, FHUProj sel ns) (HList (: * e l), Proxy HNat n) | |
(~ * ch (Proxy Bool (HBoolEQ sel (KMember n ns))), Apply (ch, FHUProj sel ns) (HList (: * e l), Proxy HNat n)) => Apply (FHUProj sel ns) (HList (: * e l), Proxy HNat n) | |
Apply (FHUProj sel ns) (HList ([] *), n) |
Polymorphic functions are not first-class in haskell. One solution is to
write an instance of ApplyAB
for a data type that takes the place of
the original function. In other words,
data Fn = Fn instance ApplyAB Fn a b where applyAB Fn a = actual_fn a
Normally you would have been able to pass around the definition actual_fn.
Type inference / Local functional dependencies
Note that class ApplyAB
has three parameters and no functional dependencies.
Instances should be written in the style:
instance (int ~ Int, double ~ Double) => ApplyAB Fn int double where applyAB _ = fromIntegral
rather than the more natural
instance ApplyAB Fn Int Double
The first instance allows types to be inferred as if we had
class ApplyAB a b c | a -> b c
, while the second instance
only matches if ghc already knows that it needs
ApplyAB Fn Int Double
. Additional explanation can be found
in local functional dependencies
class ApplyAB f a b whereSource
No constraints on result and argument types
(~ * f1 (a -> b -> c), ~ * f2 (b -> a -> c)) => ApplyAB HFlip f1 f2 | |
(~ * y y', ~ * fg (x -> y, y' -> z), ~ * r (x -> z)) => ApplyAB Comp fg r | |
(~ * String string, Show a) => ApplyAB HShow a string | |
(~ * String string, Read a) => ApplyAB HRead string a | |
(~ * io (IO ()), Show x) => ApplyAB HPrint x io | |
~ * hJustA (HJust a) => ApplyAB HFromJust hJustA a | |
(~ * x (e, HList l), ~ * y (HList (: * e l))) => ApplyAB FHCons x y | |
(HZip3 a b c, ~ * x (HList a, HList b), ~ * y (HList c)) => ApplyAB HZipF x y | |
(~ * (Tagged k l (Proxy * t)) a, ~ * b (Tagged k l (Maybe t))) => ApplyAB HMaybeF a b | |
~ * e' e => ApplyAB HRmTag (e, t) e' | |
~ * hJustA (HJust a) => ApplyAB (HJust t) a hJustA |
|
(Monad m, ApplyAB f x fx, ~ * fx (m ()), ~ * pair (x, m ()), ApplyAB f x (m ())) => ApplyAB (HSeq f) pair fx | |
~ * et (e, t) => ApplyAB (HAddTag t) e et | |
(~ * l [e'], ApplyAB f e e', ~ * el (e, l)) => ApplyAB (Mapcar f) el l | |
(ApplyAB f (x, y) z, ~ * mz (m z), ~ * mxy (m x, m y), Applicative m) => ApplyAB (LiftA2 f) mxy mz | |
HMapCxt f as bs as' bs' => ApplyAB (HMap f) as bs | |
(Data b, ~ * x (t, c (b -> r)), ~ * y (c r)) => ApplyAB (GunfoldK c) x y | |
(Data d, ~ * (c (d -> b), d) x, ~ * (c b) y) => ApplyAB (GfoldlK c) x y | |
ApplyAB f e e' => ApplyAB (MapCar f) (e, HList l) (HList (: * e' l)) | |
(~ * x' x, ~ * y' y) => ApplyAB (x' -> y') x y | note this function will only be available at a single type
(that is, |
(ApplyAB f a b, ApplyAB g b c) => ApplyAB (HComp g f) a c | |
(FunCxt k cxt b, ~ * (FunApp k1 geta b) a) => ApplyAB (Fun' k k1 cxt geta) a b | |
(FunCxt k cxt a, ~ * (FunApp k1 getb a) b) => ApplyAB (Fun k k1 cxt getb) a b |
Fun
can be used instead of writing a new instance of
ApplyAB
. Refer to the definition/source for the the most
concise explanation. A more wordy explanation is given below:
A type signature needs to be provided on Fun
to make it work.
Depending on the kind of the parameters to Fun
, a number of
different results happen.
ex1
A list of kind [* -> Constraint]
produces those
constraints on the argument type:
>>>
:set -XDataKinds
>>>
let plus1 = Fun (\x -> if x < 5 then x+1 else 5) :: Fun '[Num, Ord] '()
>>>
:t applyAB plus1
applyAB plus1 :: (Num a, Ord a) => a -> a
Also note the use of '()
to signal that the result
type is the same as the argument type.
A single constraint can also be supplied:
>>>
let succ1 = Fun succ :: Fun Enum '()
>>>
:t applyAB succ1
applyAB succ1 :: Enum a => a -> a
>>>
let just = Fun Just :: Fun '[] Maybe
>>>
:t applyAB just
applyAB just :: a -> Maybe a
see Fun
. The only difference here is that the argument
type is calculated from the result type.
>>>
let rd = Fun' read :: Fun' Read String
>>>
:t applyAB rd
applyAB rd :: Read b => [Char] -> b
>>>
let fromJust' = Fun' (\(Just a) -> a) :: Fun' '[] Maybe
>>>
:t applyAB fromJust'
applyAB fromJust' :: Maybe b -> b
Note this use of Fun' means we don't have to get the b out of Maybe b
,
type family FunCxt cxts a :: ConstraintSource
Simple useful instances of Apply
print. An alternative implementation could be:
>>>
let hPrint = Fun print :: Fun Show (IO ())
This produces:
>>>
:t applyAB hPrint
applyAB hPrint :: Show a => a -> IO ()
read
>>>
applyAB HRead "5.0" :: Double
5.0
Compose two instances of ApplyAB
>>>
applyAB (HComp HRead HShow) (5::Double) :: Double
5.0
HComp g f | g . f |
app Comp (f,g) = g . f
. Works like:
>>>
applyAB Comp (succ, pred) 'a'
'a'
>>>
applyAB Comp (toEnum :: Int -> Char, fromEnum) 10
10
Note that defaulting will sometimes give you the wrong thing
used to work (with associated types calculating result/argument types) >>> applyAB Comp (fromEnum, toEnum) 'a' *** Exception: Prelude.Enum.().toEnum: bad argument
((a,b) -> f a >> b)
HSeq x |
Proxy
see Data.HList.Proxy
A special Proxy
for record labels, polykinded
~ k x x' => ToSym * k (Label k x) x' | for Data.HList.Label6 labels |
IsKeyFN * (Label Symbol s -> a -> b) True | labels that impose no restriction on the type of the (single) argument which follows
|
Show desc => Show (Label * (Lbl k * x ns desc)) |
labelToProxy :: Label l -> Proxy lSource
Booleans
GHC already lifts booleans, defined as
data Bool = True | False
to types: Bool becomes kind and True and False (also denoted by 'True and 'False) become nullary type constructors.
The above line is equivalent to
data HTrue data HFalse
class HBool x instance HBool HTrue instance HBool HFalse
Value-level proxies
Conjunction
Disjunction
Compare with the original code based on functional dependencies:
class (HBool t, HBool t', HBool t'') => HOr t t' t'' | t t' -> t'' where hOr :: t -> t' -> t''
instance HOr HFalse HFalse HFalse where hOr _ _ = hFalse
instance HOr HTrue HFalse HTrue where hOr _ _ = hTrue
instance HOr HFalse HTrue HTrue where hOr _ _ = hTrue
instance HOr HTrue HTrue HTrue where hOr _ _ = hTrue
Boolean equivalence
Naturals
The data type to be lifted to the type level
HEq HNat HZero HZero True | |
HEq HNat HZero (HSucc n) False | |
HEq HNat (HSucc n) HZero False | |
HEq HNat n n' b => HEq HNat (HSucc n) (HSucc n') b | |
HTypes2HNats [*] [*] ([] *) l ([] HNat) | And lift to the list of types |
(HType2HNat k [*] e l n, HTypes2HNats [k] [*] es l ns) => HTypes2HNats [k] [*] (: k e es) l (: HNat n ns) | |
HLookupByHNat n l => Apply (FHLookupByHNat l) (Proxy HNat n) | |
HNat2Integral n => Show (Proxy HNat n) | |
Apply (FHUProj sel ns) (HList l, Proxy HNat (HSucc n)) => Apply (Proxy Bool False, FHUProj sel ns) (HList (: * e l), Proxy HNat n) | |
Apply (Proxy Bool True, FHUProj sel ns) (HList (: * e l), Proxy HNat n) | |
(~ * ch (Proxy Bool (HBoolEQ sel (KMember n ns))), Apply (ch, FHUProj sel ns) (HList (: * e l), Proxy HNat n)) => Apply (FHUProj sel ns) (HList (: * e l), Proxy HNat n) |
class HNat2Integral n whereSource
hNat2Integral :: Integral i => Proxy n -> iSource
HNat2Integral HZero | |
HNat2Integral n => HNat2Integral (HSucc n) |
type family HNatEq t1 t2 :: BoolSource
Equality on natural numbers (eventually to be subsumed by the universal polykinded HEq)
Maybies
We cannot use lifted Maybe since the latter are not populated
HJust x |
(Apply p s, HUnfold' p (ApplyR p s)) => HUnfold' p (HJust (e, s)) | |
Show x => Show (HJust x) | |
~ * hJustA (HJust a) => ApplyAB (HJust t) a hJustA |
|
FromHJust l => FromHJust (: * (HJust e) l) | |
ToHJust l l' => ToHJust (: * e l) (: * (HJust e) l') |
Polykinded Equality for types
class HEq x y b | x y -> bSource
We have to use Functional dependencies for now, for the sake of the generic equality.
~ Bool False b => HEq k x y b | |
HEq k x x True | |
HEq HNat HZero HZero True | |
HEq HNat HZero (HSucc n) False | |
HEq HNat (HSucc n) HZero False | |
HEq HNat n n' b => HEq HNat (HSucc n) (HSucc n') b | |
HEq [k] ([] k) ([] k) True | |
HEq [k] ([] k) (: k e l) False | |
HEq [k] (: k e l) ([] k) False | |
(HEq k e1 e2 b1, HEq [k] l1 l2 b2, ~ Bool br (HAnd b1 b2)) => HEq [k] (: k e1 l1) (: k e2 l2) br |
Staged equality
Type-safe cast -- no longer need. We use a a ~ b
Error messages
module Data.Proxy