Showing posts with label GADTs. Show all posts
Showing posts with label GADTs. Show all posts

Friday, February 22, 2008

Types, named (was: "Name that type!"), plus two more questions

Here are the answers to the previous quiz.

The first datatype holds any factorial number of values. Here it is again, with one small change and a few de-obfuscations:


> data FacHelp (f :: * -> *) (y :: *) = 
>      Stop y
>    | More (FacHelp (OneMore f) (f y))
> newtype OneMore (f :: * -> *) (y :: *) = 
>      OneMore (y, f y)
> newtype Identity y = Identity y
> type Factorial y = FacHelp Identity y

The second is lists of length more than two containing lists of length more than two. This means that no inhabitant contains a prime number of elements. As far as I know, it is unknown whether there is a datatype with every inhabitant containing a prime number of elements. (GADTs can do it, of course, but I'm ignoring those for now, since they can do anything. (mirror, mirror 2, mirror 3, mirror 4)

Here's that datatype again, renamed:


> data LongList x = Two x x
>                 | AndAnother x (LongList x)
> data Composite x = Composite (LongList (LongList x))

Update:
Chung-chieh Shan pointed out to me that I got that datatype wrong. (The other text above is correct, I think.) Anyway, here's his counterexample:

> test :: Composite Char
> test = Composite (Two (Two 'a' 'b')
>                       (AndAnother 'c' (Two 'd' 'e')))

This is a correct (I think!) implementation of a container that can't be of a prime size:

> data Product f g a = Times (f (g a))
>                    | LeftIncr (Product (OneMore f) g a)
>                    | RightIncr (Product f (OneMore g) a)
> newtype Pair a = Pair (a,a)
> type Comp = Product Pair Pair

End update

The third datatype uses the type H, which encodes containers with a square number of elements. H is based on the idea that the sequence of partial sums of the sequence of odd natural numbers is the same as the sequence of squares. That is, {0, 1, 1+3, 1+3+5, …} = {02, 12, 22, 32, …}

We can now use Lagrange's four-square theorem to see that question 3(b) is, strangely, a container that can hold any number of elements. An extension of this by Legendre showed [cite] that every number not of the form 4k(8m + 7) is representable by the sum of three squares, and this answers question 3(a).

Here are both parts again, renamed and cleaned up a bit:


> data SqHelp x y = End
>                 | Extra x (SqHelp (y,y,x) y)
> type Square a = SqHelp a a
> data Legendre x = Legendre (Square x) (Square x) (Square x)
> data Lagrange x = Lagrange (Square x) (Legendre x)

Here are two more questions:
4. Can we write something analogous to 3(a) in a more direct way?
5.

> data M a b = N b
>            | O (M (a,b) a)
> newtype P a = P (M a a)

Name that type! (nested types edition)

If we want to write a type for binary trees in Haskell, it is easy to do so:


> data BinaryTree a = Leaf
>                   | InternalNode (BinaryTree a) a (BinaryTree a)

The structure of the type guarantees certain things about its inhabitants. For instance, every node has exactly two children. If we want to guarantee stronger invariants, we can change the shape of the type. We can write a a type for complete binary tree (that is, binary trees in which every leaf is at the same depth), as

> data CompleteBinaryTree a = Leaves
>                           | NonLeaves a (CompleteBinaryTree (a,a))

The argument in the recursive part of this datatype is not the same as it is in the declaration. Datatypes with this property are called nested or non-regular or heterogeneous or non-uniform. We can express all sorts of invariants with these datatypes. Here's another example:

> data AList a b = Nil
>                | Cons a (AList b a)

If we want to manipulate lists that alternate between two types a and b, we could use the type [Either a b], but this doesn't ensure that we don't have two as or two bs in a row. We could, instead, use the type [(a,b)], but this would only allow us to create lists of even length. By switching the position of the parameters in the recursion in AList, we can represent alternating lists of any length.

Here are some other examples of data structure invariants we can ensure with nested types:

Finally, here are 3.5 examples of nested types I haven't seen elsewhere. See if you can figure out what they do.

> data A x y = B (x y)
>            | C (A (D x) (x y))
> newtype D x y = D (y,x y)
> newtype K x = K x
> type QuestionOne = A K
>
> data E x = F x x
>          | G x (E x)
> data QuestionTwo x = Q2 (E (E x))
>
> data H x y = I
>            | J y x x (H (y,x) y)
> type L = H ()
> data QuestionThreePartA x = Q3a (L x) (L x) (L x)
> data QuestionThreePartB x = Q3b (L x) (QuestionThreePartA x)

Wednesday, January 9, 2008

Extra type safety using polymorphic types as first-level refinements

This post is literate Haskell.

I will demonstrate a new technique for using polytypes as first-level refinement types. (mirror). The goal, as usual, is for types to better express program invariants and ensure programs are safe.

I'm going to demonstrate using the risers function, as presented in Dana N. Xu's ESC/Haskell (mirror), which references Neil Mitchell's Catch.


> {-# OPTIONS -fglasgow-exts #-}
> -- The LANGUAGE pragma is usually a pain for exploratory programming.

Below are the risers functions as presented by Xu and Mitchell. They are the same function, though slightly syntacticly different. risers returns the sorted sublists of a list.

Risers has two properties that we are going to discuss:

  1. None of the lists in the returned value are empty
  2. If the argument is non-empty, the return value is also non-empty.

> risersXu :: (Ord t) => [t] -> [[t]]
> risersXu [] = []
> risersXu [x] = [[x]]
> risersXu (x:y:etc) =
>     let ss = risersXu (y : etc)
>     in case x <= y of
>          True -> (x : (head ss)) : (tail ss)
>          False -> ([x]) : ss
> 
> risersMitchell :: Ord a => [a] -> [[a]]
> risersMitchell [] = []
> risersMitchell [x] = [[x]]
> risersMitchell (x:y:etc) = if x <= y
>                            then (x:s):ss
>                            else [x]:(s:ss)
>     where (s:ss) = risersMitchell (y:etc)

Neither one of these functions is obviously safe. Xu uses head and tail and ESC/Haskell to create a proof of their safety. The unsafe part of Mitchell's code is the where clause, and Mitchell also presents a tool to prove this safe.

Our goal will be to write this function in a safe way, with a type signature that ensures the two properties we expect from the function. We also aim to do this without having to change the shape of the code, only the list implementation we are using.

The present unsafe operations in risersXu and risersMitchell depend on the second property of the function: non-null inputs generate non-null outputs. We could write a type for this functions using a trusted library with phantom types for branding (paper mirror). This technique (called lightweight static capabilities) can do this and much else as well, but since clients lose all ability to pattern match (even in case), risers becomes much more verbose. We could also write a type signature guaranteeing this by using GADTs. Without using one of these, incomplete pattern matching or calling unsafe head and tail on the result of the recursive call seems inevitable.

Here's another way to write the function which does away with the need for the second property on the recursive call, substituting instead the need for the first property that no lists in the return value are empty:


> risersAlt :: (Ord t) => [t] -> [[t]]
> risersAlt [] = []
> risersAlt (x:xs) = 
>     case risersAlt xs of
>       [] -> [[x]]
>       w@((y:ys):z) -> 
>           if x <= y
>           then (x:y:ys):z
>           else ([x]):w
>       ([]:_) -> error "risersAlt"

The error is never reached.

Though ensuring the second property with our usual types seems tricky, ensuring the first is not too tough:


> type And1 a = (a,[a])
> 
> risersAlt' :: Ord a => [a] -> [And1 a]
> risersAlt' [] = []
> risersAlt' (x:xs) = 
>     case risersAlt' xs of
>       [] -> [(x,[])]
>       w@(yys:z) -> 
>           if x <= fst yys
>           then (x,fst yys : snd yys):z
>           else (x,[]):w

It is now much easier to see that risers is safe: There is one pattern match and one case, and each is simple. No unsafe functions like head or tail are called. It does have three disadvantages, however.

First, the second property is still true, but the function type does not enforce it. This means that any other callers of risers may have to use incomplete pattern matching or unsafe functions, since they may not be so easy to transform. It is my intuition that it is not frequently the case that these functions are tricky to transform, but perhaps Neil Mitchell disagrees.

We could fix this by writing another risers function with type And1 a -> And1 (And1 a), but this brings us to the second problem: And1 a is not a subtype of [a]. This means that callers of our hypothetical other risers function (as well as consumers of the output from risersAlt') must explicitly coerce the results back to lists.

Finally, if we are wrong about the first property, and risers does return an empty list for some non-empty input i, then for any x, risers (x:i) is _|_, while risersAlt' (x:i) is [(x,[])]. Thus, the equivalence of these two function definitions depends on the truth of the second property on the first function, which is something we were trying to get out of proving in the first place! Of course, if we're interested in the correctness of risersAlt', rather than its equivalence with risersXu or risersMitchell, then it is not to difficult to reason about. But part of the point of this was to get they compiler to do some of this reasoning for us, without having to change the shape of the code.

Let's write more expressive signatures using something we may have noticed when using GHCi. The only value of type forall a. [a] (excluding lists of _|_s) is []. Every value of type forall a . Maybe a is Nothing, and every forall a . Either a Int has an Int in Right. Andrew Koenig noticed something similar when learning ML (archived), and the ST monad operates on a similar principle. (paper with details about ST, mirror)


> data List a n = Nil n
>               | forall r . a :| (List a r)

The only values of type forall a . List a n use the Nil constructor, and the only values of type forall n . List a n use the :| constructor.


> infixr :|
> 
> box x = x :| Nil ()
> 
> type NonEmpty a = forall n . List a n
> 
> onebox :: NonEmpty Int
> onebox = box 1
> 
> onebox' :: List Int Dummy
> onebox' = onebox
> 
> data Dummy
> 
> -- This doesn't compile
> -- empty :: NonEmpty a
> -- empty = Nil ()

NonEmpty a is a subtype of List a x for all types x.


> data Some fa = forall n . Some (fa n)
> 
> safeHead :: NonEmpty a -> a
> safeHead x = unsafeHead x where
>     unsafeHead (x :| _) = x
> 
> safeTail :: NonEmpty a -> Some (List a)
> safeTail x = unsafeTail x where
>     unsafeTail (_ :| xs) = Some xs

Unfortunately, we'll be forced to Some and un-Some some values, since Haskell does not have first-class existentials, and it takes some thinking to see that safeHead and safeTail are actually safe.

Here is a transformed version of Mitchell's risers:


> risersMitchell' :: Ord a => List a n -> List (NonEmpty a) n
> risersMitchell' (Nil x) = (Nil x)
> risersMitchell' (x :| Nil _) = box (box x)
> risersMitchell' ((x {- :: a -}) :| y :| etc) =
>     case risersMitchell' (y :| etc) {- :: NonEmpty (NonEmpty a) -} of
>       Nil _ -> error "risersMitchell'"
>       s :| ss -> if x <= y
>                  then (x :| s) :| ss
>                  else (box x) :| s :| ss

Since we can't put the recursive call in a where clause, we must use a case with some dead code. The type annotations are commented out here to show they are not needed, but uncommenting them shows that the recursive call really does return a non-empty lists, and so the Nil case really is dead code.

This type signature ensures both of the properties listed when introducing risers. The key to the non-empty-arguments-produce-non-empty-results property is that the variable n in the signature is used twice. That means applying risersMitchell' to a list with a fixed (or existential) type as its second parameter can't produce a NonEmpty list.


> risersXu' :: Ord a => List a r -> List (NonEmpty a) r
> risersXu' (Nil x) = Nil x
> risersXu' (x :| Nil _) = box (box x)
> risersXu' (((x :: a) :| y :| etc) :: List a r) = 
>     let ss = risersXu' (y :| etc)
>     in case x <= y of
>          True -> case safeTail ss of
>                    Some v -> (x :| (safeHead ss)) :| v
>          False -> (box x) :| ss

Here we see that the type annotation isn't necessary to infer that risers applied to a non-empty list returns a non-empty list. The value ss isn't given a type signature, but we can apply safeHead and safeTail. The case matching on safeTail is the pain of boxing up existentials.

This is the first version of risers with a type signature that gives us the original invariant Xu and Mitchell can infer, as well as calling no unsafe functions and containing no incomplete case or let matching. It also returns a list of lists, just like the original function, and has a definition in the same shape.

With first-class existentials, this would look just like Xu's risers (modulo built-in syntax for lists). With let binding for polymorphic values, risersMitchell' would look just like Mitchell's original risers, but be safe by construction. Let binding for polymorphic values would also allow non-trusted implementations of safeTail and safeHead to be actually safe.

For contrast, here is a GADT implementation of risers:


> data GList a n where
>     GNil :: GList a IsNil
>     (:||) :: a -> GList a m -> GList a IsCons
> 
> infixr :||
> 
> data IsNil
> data IsCons
> 
> gbox x = x :|| GNil
> 
> risersMitchellG :: Ord a => GList a n -> GList (GList a IsCons) n
> risersMitchellG GNil = GNil
> risersMitchellG (c :|| GNil) = gbox $ gbox c
> risersMitchellG (x :|| y :|| etc) =
>     case risersMitchellG (y :|| etc) of
> --    GHC complains, "Inaccessible case alternative: Can't match types `IsCons' and `IsNil'
> --    In the pattern: GNil"
> --    GNil -> error "risers" 
>       s :|| ss ->
>           if x <= y
>           then (x :|| s) :|| ss
>           else (gbox x) :|| s :|| ss

This is safe and has its safety checked by GHC. It also does not require existentials, though when using this encoding, many other list functions (such as filter) will.

Now here is a lightweight static capabilities version of risers:


> -- module Protected where
> 
> -- Export type constructor, do not export value constructor
> newtype LWSC b a = LWSC_do_not_export_me [a]
> 
> data Full
> type FullList = LWSC Full
> 
> data Any
> 
> lnil :: LWSC Any a
> lnil = LWSC_do_not_export_me []
> 
> lcons :: a -> LWSC b a -> FullList a
> lcons x (LWSC_do_not_export_me xs) = LWSC_do_not_export_me (x:xs) 
> 
> lwhead :: FullList a -> a
> lwhead (LWSC_do_not_export_me x) = head x
> 
> data Some' f n = forall a . Some' (f a n)
> 
> lwtail :: FullList a -> Some' LWSC a
> lwtail (LWSC_do_not_export_me a) = Some' (LWSC_do_not_export_me (tail a))
> 
> deal :: LWSC b a -> LWSC Any c -> (FullList a -> FullList c) -> LWSC b c
> deal (LWSC_do_not_export_me []) _ _ = LWSC_do_not_export_me []
> deal (LWSC_do_not_export_me x) _ f = 
>     case f (LWSC_do_not_export_me x) of
>       LWSC_do_not_export_me z -> LWSC_do_not_export_me z
> 
> nullcase :: LWSC b a -> c -> (FullList a -> c) -> c
> nullcase (LWSC_do_not_export_me []) z _ = z
> nullcase (LWSC_do_not_export_me x) _ f = f (LWSC_do_not_export_me x)
> 
> -- module Risers where
> 
> lbox x = lcons x lnil
> 
> risersXuLW :: Ord a => LWSC b a -> LWSC b (FullList a)
> risersXuLW x = 
>     deal x
>     lnil
>     (\z -> let x = lwhead z
>            in case lwtail z of
>                 Some' rest -> 
>                     nullcase rest
>                     (lbox (lbox x))
>                     (\yetc -> 
>                          let y = lwhead yetc
>                              etc = lwtail yetc
>                              ss = risersXuLW yetc
>                          in if x <= y
>                             then case lwtail ss of
>                                    Some' v -> lcons (lcons x $ lwhead ss) v
>                             else lcons (lbox x) ss))

There is a good bit of code that must go in the protected module, including two different functions for case dispatch. These functions are used instead of pattern matching, and make the definition of risers much more verbose, though I may not have written it with all the oleg-magic possible to make it more usable.

Out of the three, I think GADTs are still the winning approach, but it's fun to explore this new idea, especially since Haskell doesn't have traditional subtyping.

Sunday, February 18, 2007

Conor's Rule?

Edwin Brady writes in "How to write programs in two easy steps", that

I think it's also an area where dependently typed languages like Epigram will shine, because it will one day be possible to write verified interpreters for domain specific languages …
I immediately thought "I can write dependently typed programs right now with GADTs, and I can do so in GHC Haskell, Scala, or the upcoming C#". This is, of course, only mostly true, since none of these does termination checking, and the syntax is awkward.

The same blog post later references Greenspun's Tenth Rule:

Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp
and I thought that my programs follow a similar pattern with dependent types: I'm always simulating them informally. Based on Section 6.1: "The Trouble with Faking It" in Conor McBride's Faking It (Simulating Dependent Types in Haskell) (mirror), I wonder if there's any truth to something we might call
Conor's Rule
Any sufficiently complicated Haskell or ML program contains an ad hoc, informally-specified, bug-ridden, half-completed simulation of dependent types.

Wednesday, February 7, 2007

C++ and GADTs

I just noticed this post about GADTs in C++. It's more about C++'s warts than GADTs, but it's interesting nonetheless.

I also noticed recently, while preparing for my upcoming talk at the theory lunch (mirror, suspected future permalink, presently 404), that because the constructors in the OOP version of GADTs each get their own class, the class name can function as the constructor tag. This leads to what is called the Curiously Recurring Template Pattern in C++. Example:

Haskell C++
data Nat t where
    Z :: Nat ZTag
    S :: Nat n -> Nat (STag n)
data ZTag
data STag n
template<class T>
class nat {};

class zero : nat<zero> {};

template<class N>
class succ : nat<succ<N> > {
public:
  succ<N>(nat<N> x) : pred(x) {}
  nat<N> pred;
};

Of course, the tags in the Haskell version could have the same names as the constructors, since the constructor and type namespaces in Haskell are disjoint, but they would still have distinct meanings, unlike in the C++ code.

Monday, January 29, 2007

Quotient Types for Information Hiding

Imagine you're writing a datatype for representing sets. You want to hide the internal representation of sets from clients, but you also want to allow clients to perform any operations as efficiently as if the internal representation were exposed. This is not easy, and it's why Data.Set has 36 functions, when all that are really needed to ensure the meaning of a set ("These elements are distinct") are toList and fromList. Of course, writing member :: Ord a => a -> Set a -> Bool as

member x y = Data.List.elem x (toList y)
is just awful, but the price we pay for efficient member is having to open up the module and break the abstraction if one of the 34 other functions doesn't do exactly what we want. In addition to this pain, there's some danger: functions like mapMonotonic are not really safe, and cannot be made so.

The papers I've been reading about quotient types:

We could alleviate all of this mess with quotient types. Quotient types allow modules to expose their internals with no danger of breaking abstractions. The quotients are the same as the mathematical quotients in abstract algebra, where they are used frequently. Back on the type theory side, the elimination rule for typing quotients depends not just on types, but on terms, and so requires a type system with dependent types. That's a shame, since dependent types are so tricky to typecheck. It would be great if there were some form of lightweight quotient types that didn't require the full power of dependent types.

The break-the-abstraction vs. lose-efficiency-&-safety issue reminds me of the difference between GADTs and lightweight static capabilities: GADTs are verbose, but they allow the client to safely and efficently operate on datatypes in ways that aren't covered by the library.

Thursday, January 11, 2007

Type (Dis)equality

Type equality is the essential ingredient in guarded algebraic datatypes:

data Z ; data S n ;

data ListGADT a n where
    NilListGADT :: ListGADT a Zero
    ConsListGADT :: a -> ListGADT a n -> ListGADT a (S n)

data TypeEqual a b = . . .
data ListEq a n = NilListEq (TypeEqual n Z)
                | forall m . ConsListEq (TypeEqual n (S m)) a (ListEq a m)
What new power would type disequality bring? It might prevent infinite types.
data WrapList a = forall n . WrapList (ListGADT a n)

cons :: a -> WrapList a -> WrapList a
cons x (WrapList y) = WrapList (Cons x y)

ones = cons 1 ones
Here, the size type inside ones is S (S (S . . . . That's bad. What if we wrote:
data TypeDisEq a b where
    DisEqLess :: DisEq Z (S a)
    DisEqMore :: DisEq (S a) Z
    DisEqInduct :: DisEq a b -> DisEq (S a) (S b)

data ListGADTDisEq a n where
    NilListGADTDisEq :: ListGADTDisEq a Zero
    ConsListGADTDisEq :: a -> ListGADTDisEq a n -> (TypeDisEq n (S n)) -> ListGADTDisEq a (S n)
Now, since the size type inside a ListGADTDisEq can't be one more than itself, it's finite, and we might not be able to form ones. Sadly, that's not true:
data WrapListDisEq a = forall n . WrapListDisEq (ListGADTDisEq a n)

cons' :: a -> WrapListDisEq a -> WrapListDisEq a
cons' x (WrapListDisEq NilListGADTDisEq) = WrapListDisEq (ConsListGADTDisEq x Nil DisEqBase)
cons' x (WrapListDisEq p@(Cons q r s)) = WrapListDisEq (ConsListGADTDisEq x p (DisEqInduct s))

ones' = cons' 1 ones'