ghci -fglasgow-exts -fallow-undecidable-instances



X2 = U+X







> diagonal x = (x,x)



> class Eq x => AntiDiagonal u x | x -> u where

> twine :: (x,x) -> Either x u

> untwine :: Either x u -> (x,x)



> twine' :: (x,x) -> u

> untwine' :: u -> (x,x)



> twine (x,y) | x==y = Left x

> | otherwise = Right $ twine' (x,y)



> untwine (Left x) = (x,x)

> untwine (Right y) = untwine' y





twine

untwine

twine'

untwine'

twine'





> instance AntiDiagonal Bool Bool where

> twine' (a,b) = a

> untwine' a = (a,not a)





twine

untwine

twine . diagonal

Left

Bool

Bool





data Bool' = False'





Bool

Bool

Bool

Theorem

Proof



x = u+v+2ab





y = au+bv+uv.



2

2

2

2

2

2

2

2

2

Either a b

Left _

Left u'

Left v'

Left u'

Right v'





> data Either' a b u v = BothLeft u | BothRight v | LeftAndRight Bool a b



> instance (AntiDiagonal u a,AntiDiagonal v b) => AntiDiagonal (Either' a b u v) (Either a b) where

> twine' (Left x,Left y) = BothLeft (twine' (x,y))

> twine' (Right x,Right y) = BothRight (twine' (x,y))

> twine' (Left x,Right y) = LeftAndRight False x y

> twine' (Right x,Left y) = LeftAndRight True y x



> untwine' (BothLeft u) = let (a,b) = untwine' u in (Left a,Left b)

> untwine' (BothRight v) = let (a,b) = untwine' v in (Right a,Right b)

> untwine' (LeftAndRight False x y) = (Left x,Right y)

> untwine' (LeftAndRight True x y) = (Right y,Left x)





(a,b)





> data Pair' a b u v = LeftSame a v | RightSame u b | BothDiffer u v deriving (Eq,Show)



> instance (AntiDiagonal u a,AntiDiagonal v b) => AntiDiagonal (Pair' a b u v) (a,b) where

> twine' ((a,b),(a',b')) | a==a' = LeftSame a (twine' (b,b'))

> | b==b' = RightSame (twine' (a,a')) b

> | otherwise = BothDiffer (twine' (a,a')) (twine' (b,b'))



> untwine' (LeftSame a v) = let (b,b') = untwine' v in ((a,b),(a,b'))

> untwine' (RightSame u b) = let (a,a') = untwine' u in ((a,b),(a',b))

> untwine' (BothDiffer u v) = let (a,a') = untwine' u

> (b,b') = untwine' v

> in ((a,b),(a,b'))





Either () ()

Bool

Bool

Bool





> type Natural = Integer









> instance AntiDiagonal (Bool,Natural,Natural) Natural where

> twine' (a,b) | compare a b == GT = (False,a-b,b)

> | compare a b == LT = (True,b-a,a)

> untwine' (False,d,b) = (b+d,b)

> untwine' (True,d,a) = (a,a+d)





compare





> data N = Zero | S N deriving (Eq,Show)





2

2

2

2





> data M = Loop M | Finish Bool N deriving Show



> instance AntiDiagonal M N where

> twine' (Zero,S x) = Finish False x

> twine' (S x,Zero) = Finish True x

> twine' (S x,S y) = Loop (twine' (x,y))

> untwine' (Finish False x) = (Zero,S x)

> untwine' (Finish True x) = (S x,Zero)

> untwine' (Loop m) = let (a,b) = untwine' m in (S a,S b)





Either

Natural

Bool

(Bool,Natural,Natural)

twine

S (S (S Zero))

S (S (S (S Zero)))

S (S (S _))

Loop

Finish

2

2

[Bool]

2



P = L 2

= (1+2L) 2

= 1 2 +(2L) 2 +4L

= (2L 2 +2 2 L+2 2 L 2 )+4L

= 2P+2P+2L+4L

= 4P+6L



twine

untwine





> data SharedList = LeftNil Bool [Bool] | RightNil Bool [Bool]

> | HeadSame Bool SharedList | TailSame Bool [Bool]

> | Diff Bool SharedList deriving Show



> instance AntiDiagonal SharedList [Bool] where

> twine' ([],b:t) = LeftNil b t

> twine' (b:t,[]) = RightNil b t

> twine' (a:b,a':b') | a==a' = HeadSame a (twine' (b,b'))

> | b==b' = TailSame a b

> | otherwise = Diff a (twine' (b,b'))



> untwine' (LeftNil b t) = ([],b:t)

> untwine' (RightNil b t) = (b:t,[])

> untwine' (HeadSame a b) = let (t1,t2) = untwine' b in (a:t1,a:t2)

> untwine' (TailSame a b) = (a:b,not a:b)

> untwine' (Diff a b) = let (t1,t2) = untwine' b in (a:t1,not a:t2)





SharedList

Int

Double

n

n





data BoolTree = Leaf Bool | Fork BoolTree BoolTree





(Integer,Integer)

[Integer] 2

In a programming language with constrained types we can construct a type like "the type of pairs of X's where the two X's are distinct". But can we make such a type in Haskell? Answering this question will take us on a long journey which I think I'm going to split over three parts. And as usual, just put this blog post into a file called main.lhs and you can run it withWe can write our question a little more formally. Given a type X, can we form a type U with the property thatThe idea is that the = sign is an isomorphism with the property that the diagonal in X, ie. elements of the form (x,x), get mapped to the right component of U+X. When we are able to do this, we'll call U the antidiagonal of X, and say that X is splittable.We can express the relationship between U and X through a multiparameter type classThe isomorphism between Xand X+U is given byand. But to save writing similar code over and over again, and to ensure we really are mapping the diagonal of Xcorrectly, we define these in terms ofand. (Note thatis partial, it's only guaranteed to take a value off the diagonal.)Just to get into the swing of things, here's a simple example:It's not hard to check thatandare mutual inverses and that. You can view this as a special case of 2=2+2 as 2 is essentially a synonym forIt looks a lot like what we're trying to do is subtract types by forming X-X or X(X-1). Much as I've enjoyed trying to find interpretations of conventional algebra and calculus in the context of the algebra of types, subtraction of types, in general, really doesn't make much sense. Consider what we might mean by X-1. If X=then we could simply defineThis certainly has some of the properties you might expect of-1, such as having only one instance. But it's not very natural. How should we embed this type back in? There are two obvious ways of doing it and neither stands out as better than the other, and neither is a natural choice. But what we've shown above is that there is a natural way to subtract X from Xbecause a copy of X appears naturally in Xas the diagonal. So the question is, can we extend this notion to types beyondHow about a theorem. (I'll give a more intuitive explanation below.)In any commutative semiring fix a and b. If the equation a=u+a has a solution, and the equation b=v+b has a solution, then the equations (a+b)=x+(a+b) and (ab)=y+ab also have solutions.Simply defineandQEDSo if some types are splittable, so are their sums and products. As types form a commutative semiring we see that this theorem, twined with 1=1+0 allows us to form "X(X-1)" for any type X built purely using non-recursive Haskell data declarations. In fact, we can use the above theorem to define "X(X-1)" for types, and use the notation Xfor this. (I hope your browser shows that the exponent here is underlined.) There's a reason I use this notation which I'll get to later. So (a+b)=a+b+2ab and (ab)=ab+ba+aHere's a more intuitive explanation of that theorem above. Suppose u and v are both in. Then there are several ways they could be different to each other. For example, they could both be of the formin which case u =and v =and u' and v' must be distinct. They could be of the formandin which case it doesn't matter what u' and v' are. A little thought shows there are four distinct cases in total. These can be written in algebraic notation as u+v+ab+ba=u+v+2ab. To make this clearer, here's an implementation:A similar argument can be carried through forleading to:This all works very well, but at this point it becomes clear that Haskell has some weaknesses. The typeis isomorphic toso we should be able to use the above code to construct the antidiagonal ofautomatically. But Haskell doesn't give us access to that information. We can't ask, at runtime, ifis the sum of more primitive types. There are a number of solutions to this problem - we can use various more generic types of Haskell, or use Template Haskell. But I'm just going to stick with Haskell and manually construct the antidiagonal. (I wonder what Coq has to offer here.)So I've solved the problem for 'finite' types built from singles, addition and multiplication. But what about recursive types. Before doing that, let's consider an approach to forming the antidiagonal of the naturals. Haskell has no natural type but let's pretend anywayThere's an obvious packing of a distinct pair of naturals as a pair of naturals:(I had to useto work around a blogger.com HTML bug!) It'd be cool if the code above could have been derived automatically, but alas it's not to be. But we can get something formally similar. Define the natural numbers like this:A natural is zero or the successor of a natural. Algebraically this is just N=1+N. Now we wish to find N. Using 1=0 and the earlier theorem we get M=1+N+2N, ie. M=M+2N. Let's code this up:Note that I've more or less just coded up the same thing as what I did forabove. Can you see that that this is a disguised version of the code for the antidiagonal ofabove? Think about the type N=1+N. We can view this as the set of paths through this finite state diagram, starting at N and ending at 1:Essentially there's just one path for each natural number, and this number counts how many times you loop around. Now consider the same sort of thing with the type M=M+2N, starting at M and ending at 1:We can describe such paths by the number of loops taken at M, the number of loops taken at N, and aspecifying whether we took path 0 or 1 from state 2 to state N. In other words, M is much the same thing asabove! M is a kind of 'compressed' version of a pair of N's. Suppose we want toand. Both of these share apart. What the type M does is allow you to factor out this part (that's the part that goes into) and the remainder is stored in thepart, with a boolean specifying whether it was the first or second natural that terminated first.Let's step back or a second. Earlier I showed how for any type X, built from addition, multiplication and 1, we could form X. Now we've gone better, we can now form Xeven for recursive types. (At least for data, not codata.) We haven't defined subtraction in general, but we have shown how to form X(X-1) in a meaningful way.Let's try lists of booleans next,. We can write this type as L=1+2L. Let's do the algebra above to find P = LIn other words P = 4P+6L. The easiest thing is to code this up. Remember that each '=' sign in the above derivation corresponds to an isomorphism defined byandso after lots of unpacking, and rewriting 4P+6L as 2L+2L+2P+2L+2P we getThis looks pretty hairy, but it's really just a slight extension of the M=M+2N example. What's happening is that if two lists have the same prefix, thenmakes that sharing explicit. In other words this type implements a form of compression by factoring out shared prefixes. Unfortunately it took a bit of work to code that up. However, if we were programming in generic Haskell, the above would come absolutely for free once we'd defined how to handle addition and multiplication of types. What's more, it doesn't stop with lists. If you try it with trees you automatically get factoring of common subtrees and it works with any other datatype you can build from a Haskell data declaration (that doesn't use builtin primitive types likeor).So now I can say why I used the underlined superscript notation. It's the falling factorial . More generally X=X(X-1)...(X-n+1). You may be able to guess what this actually means - it's an n-tuple with n distinct elements. Unfortunately, you can probably see that generalising the above theorem to n from 2 gets a bit messy. But (and this is what I really want to talk about) there's an amazing bit of calculus that allows you to define a generating function (more like generating functor) that gives you all of the Xin one go. But before I can talk about that I need to write a blog about generalised tries ...Here are some exercises.(1) Can you code up the antidiagonal of binary boolean trees, T = 2+T(2) There are more efficient ways to define the naturals than through the successor function. Can you come up with a more efficient binary scheme and then code up its antidiagonal?(3) The antidiagonal of the integers can be approximated by. This seems a bit useless - after all, the whole point of what I've written above is to split this up. But we can use this approximation to construct approximations of other types where you do get a payoff. Implement an approximation tothis way so that you still get the benefit of prefix sharing. This looks a lot like traditional tries

Labels: haskell, types