We can kind of simlutate dependent types in Haskell if the type is dependent on a value
which is uniquely determined by its type. Well that's not dependent types, of course,
but it can be useful sometimes.
So let's build a kind of small constructive set theory at the type level.
Each type will represent a particular function, and will be inhabited by a single value
(excluding all the bottom thing).
Define F as the smallest set satisfying the following:
id:: a -> a
is in F.
term:: a -> ()
is in F.
init:: Empty -> a
is in F (where Empty represents the empty set).
p1 :: (a,b) -> a
is in F.
i1 :: a -> Either a b
is in F.
flip :: (a,b) -> (b,a)
is in F.
- if both
f::a -> b
and g::b -> c
are in F, then g.f :: a -> c
is in F.
- if both
f::a -> b
and g::c -> d
are in F, then the
following functions are in F:
f*g :: (a,c) -> (b,d)
f*g (x,y) = (f x,g y)
f + g :: Either a b -> Either c d
(f+g) (Left x) = f x
(f+g) (Right y) = g y`
- (Add ohter inductive rules here so that your favorite functions can be included in F.)
The set F is meant to represent the functions that are encodable at the type level in Haskell,
and at the same time whose various properties such as surjectivity, injectivity, etc. are provable by
the type-level functions in Haskell.
With the help of associative types, we can encode F cleanly as follows:
class Function f where
type Dom f :: *
type Codom f :: *
apply :: f -> Dom f -> Codom f
data ID a = ID -- represents id :: a -> a
instance Function (ID a) where
type Dom (ID a) = a
type Codom (ID a) = a
apply _ x = x
data P1 a b = P1 -- represents the projection (a,b) -> a
instance Function (P1 a b) where
type Dom (P1 a b) = (a,b)
type Codom (P1 a b) = a
apply _ (x,y) = x
...
data f :.: g = f :.: g -- represents the composition (f.g)
instance ( Function f
, Function g
, Dom f ~ Codom g)
=> Function (f :.: g) where
type Dom (f :.: g) = Dom g
type Codom (f :.: g) = Codom f
apply (f :.: g) x = apply f (apply g x)
...
The type-level predicate "f is surjective" can be expressed as class instances:
class Surjective f where
instance Surjective (ID a) where
instance Surjective (P1 a b) where
instance (Surjective f,Surjective g)
=> Surjection (f :.: g) where
..
Finally a higher order function that takes those provably surjective functions can be defined:
surjTrans :: (Function fun,Surjective fun)
=> fun -> Dom fun -> Codom fun
surjTrans surj x = apply surj x
Similarly for injections, isomorphisms, etc.
For example, a higher order function that only takes (constructive) isomorphisms as arguments can be declared:
isoTrans :: (Function fun,Surjective fun,Injective fun)
=> fun -> Dom fun -> Codom fun
isoTrans iso x = apply iso x
If the transformations take a more interesting form, then it will have to be a class method and defined by a structural
recursion for each function (which is uniquely determined by its type).
I'm certainly no expert in logic or Haskell, and I would really like to know how powerful this theory can be.
If you found this, could you post an update?
Here is the full code:
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
infixl 6 :.:
infixl 5 :*:
infixl 4 :+:
data TRUE
data Empty
class Function f where
type Dom f :: *
type Codom f :: *
apply :: f -> Dom f -> Codom f
instance Function (a -> b) where
type Dom (a->b) = a
type Codom (a->b) = b
apply f x = f x
data ID a = ID
data Initial a = Initial
data Terminal a = Terminal
data P1 a b = P1
data P2 a b = P2
data I1 a b = I1
data I2 a b = I2
data FLIP a b = FLIP
data COFLIP a b = COFLIP
data f :.: g = f :.: g
data f :*: g = f :*: g
data f :+: g = f :+: g
instance Function (ID a) where
type Dom (ID a) = a
type Codom (ID a) = a
apply _ x = x
instance Function (Initial a) where
type Dom (Initial a) = Empty
type Codom (Initial a) = a
apply _ _ = undefined
instance Function (Terminal a) where
type Dom (Terminal a) = a
type Codom (Terminal a) = ()
apply _ _ = ()
instance Function (P1 a b) where
type Dom (P1 a b) = (a,b)
type Codom (P1 a b) = a
apply _ (x,y) = x
instance Function (P2 a b) where
type Dom (P2 a b) = (a,b)
type Codom (P2 a b) = b
apply _ (x,y) = y
instance Function (I1 a b) where
type Dom (I1 a b) = a
type Codom (I1 a b) = Either a b
apply _ x = Left x
instance Function (I2 a b) where
type Dom (I2 a b) = b
type Codom (I2 a b) = Either a b
apply _ y = Right y
instance Function (FLIP a b) where
type Dom (FLIP a b) = (a,b)
type Codom (FLIP a b) = (b,a)
apply _ (x,y) = (y,x)
instance Function (COFLIP a b) where
type Dom (COFLIP a b) = Either a b
type Codom (COFLIP a b) = Either b a
apply _ (Left x) = Right x
apply _ (Right y) = Left y
instance ( Function f
, Function g
, Dom f ~ Codom g)
=> Function (f :.: g) where
type Dom (f :.: g) = Dom g
type Codom (f :.: g) = Codom f
apply (f :.: g) x = apply f (apply g x)
instance (Function f, Function g)
=> Function (f :*: g) where
type Dom (f :*: g) = (Dom f,Dom g)
type Codom (f :*: g) = (Codom f,Codom g)
apply (f :*: g) (x,y) = (apply f x,apply g y)
instance (Function f, Function g)
=> Function (f :+: g) where
type Dom (f :+: g) = Either (Dom f) (Dom g)
type Codom (f :+: g) = Either (Codom f) (Codom g)
apply (f :+: g) (Left x) = Left (apply f x)
apply (f :+: g) (Right y) = Right (apply g y)
class Surjective f where
class Injective f where
class Isomorphism f where
instance Surjective (ID a) where
instance Surjective (Terminal a) where
instance Surjective (P1 a b) where
instance Surjective (P2 a b) where
instance Surjective (FLIP a b) where
instance Surjective (COFLIP a b) where
instance (Surjective f,Surjective g)
=> Surjective (f :.: g) where
instance (Surjective f ,Surjective g )
=> Surjective (f :*: g) where
instance (Surjective f,Surjective g )
=> Surjective (f :+: g) where
instance Injective (ID a) where
instance Injective (Initial a) where
instance Injective (I1 a b) where
instance Injective (I2 a b) where
instance Injective (FLIP a b) where
instance Injective (COFLIP a b) where
instance (Injective f,Injective g)
=> Injective (f :.: g) where
instance (Injective f ,Injective g )
=> Injective (f :*: g) where
instance (Injective f,Injective g )
=> Injective (f :+: g) where
instance (Surjective f,Injective f)
=> Isomorphism f where
surjTrans :: (Function fun,Surjective fun)
=> fun -> Dom fun -> Codom fun
surjTrans surj x = apply surj x
injTrans :: (Function fun,Injective fun)
=> fun -> Dom fun -> Codom fun
injTrans inj x = apply inj x
isoTrans :: (Function fun,Isomorphism fun)
=> fun -> Dom fun -> Codom fun
isoTrans iso x = apply iso x
g1 :: FLIP a b
g1 = FLIP
g2 :: FLIP a b :*: P1 c d
g2 = FLIP :*: P1
g3 :: FLIP a b :*: P1 c d :.: P2 e (c,d)
g3 = FLIP :*: P1 :.: P2
i1 :: I1 a b
i1 = I1
For example, here are some of the outputs (see how Haskell 'proves' those recursive properties when typechecking):
isoTrans g1 (1,2)
==> (2,1)
surjTrans g2 ((1,2),(3,4))
==> ((2,1),3)
injTrans g2 ((1,2),(3,4))
==> No instance for (Injective (P1 c0 d0)) ..
surjTrans i1 1 :: Either Int Int
==> No instance for (Surjective (I1 Int Int)) ..
injTrans i1 1 :: Either Int Int
==> Left 1
isoTrans i1 1 :: Either Int Int
==> No instance for (Surjective (I1 Int Int)) ..