Banner

HTTP content-type comparison at the type level

Posted by alpheccar - Dec 23 2007 at 22:24 CEST

If you read my previous post, you'll be interested by this one. Indeed, I have discovered that the idea introduced in the previous post is working but not practical at all since it does not work with type synonym definitions. But, without synonyms, you can't really write the kind of types required to do http content-type comparison at the type level. By luck, some additional type hacking is solving the problem. Moreover, I think it can be useful to show in a very informal way a few applications of computations at type level. It is not so hard (but not totally trivial. I had to make several experiments to get it right).

What is the problem ?

The order relationship (:<:), defined in my previous post, is working if the type has the shape of a list:

Haskell Code by HsColour
type T = A :+: (B :+: (C :+: D))

which can also be written:

Haskell Code by HsColour
type T = A :+: B :+: C :+: D

since (:+:) is defined with

Haskell Code by HsColour
infixr 6 :+:

When I read about type synonym for the first time in the Haskell report I was confused by the sentence:

Type synonyms are a convenient, but strictly syntactic, mechanism to make type signatures more readable.

I was stupid to think that type synonyms were a bit like preprocessing directives. It is not the case and that's why the following type is not equivalent to the one above:

Haskell Code by HsColour
type TA = A :+: B
type TB = C :+: D
type T' = TA :+: TB

If I expand the type synonyms, I get:

Haskell Code by HsColour
type T' = (A :+: B) :+: (C :+: D)

So the shape of T' is the shape of a tree and not of a list like T above.

Solution

To be able use (:<:) with types built from type synonyms, I need to define a function on types transforming a tree to a list ! Then, I'll be able to compare easily any expression built from phantom types, the type constructor (:+:) and type synonyms. I am not interested in the shape of the type. The types that I am building are just encodings for set of types.

If the function was a normal Haskell function, I could define:

Haskell Code by HsColour
data MyTypes = A | B | C | D deriving(Show)
data Tree a = (:+:) (Tree a) (Tree a) | L a deriving(Show)

flatten :: Tree MyTypes -> Tree MyTypes
flatten (L x) = L x
flatten (a :+: b) = treeConcat (flatten a) (flatten b)

treeConcat :: Tree MyTypes -> Tree MyTypes -> Tree MyTypes
treeConcat (L x) b = (L x) :+: b
treeConcat (x :+: xs) ys = x :+: (treeConcat xs ys)

t = ((L A) :+: (L B)) :+: ((L C) :+: (L D))

Just type:

Haskell Code by HsColour
flatten t 

and you'll see that's the result has the shape of a list. treeConcat is just the list concat but where the lists are implemented with the Tree datatype.

Implementation at the type level

I am just going to translate this to the type level. It is nearly a direct translation. First, let's translate the algebraic datatype.

Haskell Code by HsColour
data A
data B
data C
data D

Tree is already defined as (:+:) at the type level in the previous post, and we do not need to define the L since at the type level I can make a distinction between a phantom type and a type like (a :+: b) which is not atomic.

Now, let's translate the treeConcat:

Haskell Code by HsColour
class TreeConcat l1 l2 l | l1 l2 -> l

The recursion is:

Haskell Code by HsColour
instance (TreeConcat xs ys zs ) => TreeConcat (x :+: xs ) ys (x :+: zs )

But, I also need to write instances for the leaves. I do not have a L like in the algebraic datatype. So, I need one instance per phantom type:

Haskell Code by HsColour
instance TreeConcat A b (A :+: (b))
instance TreeConcat B b (B :+: (b))
instance TreeConcat C b (C :+: (b))
instance TreeConcat D b (D :+: (b))

I am nearly done. Now, I just need to implement flatten:

Haskell Code by HsColour
class Flatten a b | a -> b

instance (Flatten a a', Flatten b b', TreeConcat a' b' c) => Flatten (a :+: b) c

It should be easy to decode it. The flatten a of the normal definition becomes Flatten a a', the flatten b becomes Flatten b b' and the treeConcat a' b' becomes TreeConcat a' b' c'. It should be clearer if I write the normal Haskell function as:

Haskell Code by HsColour
flatten (a :+: b) = let a' = flatten a
                        b' = flatten b
                        c = treeConcat a' b' 
                    in
                        c

And, I also need several instances for the phantom types. They are replacing the line flatten (L x) = L x

Haskell Code by HsColour
instance Flatten A A  
instance Flatten B B
instance Flatten C C
instance Flatten D D

Finally, I need to change the definition of (:<:) to use flatten. The old definition was:

Haskell Code by HsColour
class (:<:) a b

instance Elem a c => (:<:) a c
instance ((:<:) a c, (:<:) b c) => (:<:) (a :+: b) c

Now, it is:

Haskell Code by HsColour
class (:<:) a b

instance (Elem a c', Flatten c c') => (:<:) a c
instance ((:<:) a c', (:<:) b c',Flatten c c') => (:<:) (a :+: b) c

You see the difference ? Instead of using c, I use c' which is the flat version.

That's it ! We can check that it is working.

Haskell Code by HsColour
testa :: ((:<:)  (A :+: C) f) => (A :+: C) -> f
testa = undefined

testb :: T -> T
testb = undefined

In ghci, type :

Haskell Code by HsColour
:t testb . testa

and you get:

Haskell Code by HsColour
testb . testa :: (A :+: C) -> T

So, (A :+: C) is included in T in as a consequence, testa can be used. Now, if you change the code into:

Haskell Code by HsColour
data E
testa :: ((:<:)  (A :+: E) f) => (A :+: E) -> f
testa = undefined

and do the same test you get:

Haskell Code by HsColour
No instance for (Elem E D)

The type checker is preventing you to use testa because A :+: E is not included in T.

Both tests are failing with the old definition for (:<:). The use of Flatten is really required.

MERRY CHRISTMAS !

Tags | | |

Comments

Add a comment...

Posted by alpheccar - Jan 16 2008 at21:03 CEST

I just want to add a remark : I don't intend to compute a lot at the type level. The lists and trees encoded at the type level will always be small ones in my applications. So, I really don't care about the algorithmic complexity of my algorithms at the type level.

That's why I chose a bad implementation of the flatten function but easy to code at the type level. There are more efficient ways to code flatten.

Posted by alpheccar - Dec 26 2007 at22:21 CEST

Thank you. The only problem I have is that I have just implemented a function (:<:)

But, it is a relation and without the transitive closure, the use of (:<:) is sometimes not very user friendly. But, so far, I have not found how to improve the implementation.

Posted by Max - Dec 26 2007 at19:29 CEST

Very cute series of posts! I'll be following the development of this HTTP library with great interest :-)