I've been playing with the Coq proof assistant over the past few days, following closely on some frustrations that I've been having with using SML's module system and a bit of toying with type-classes in Haskell.
The gist of the problem is this. Although you can define type-classes and modules such that external users of these modules/type-classes see a uniform interface, consistency is left as an exercise for the implementer. This is not really acceptable in my view. When you are writing software, often times *you* are the implementer. What you really want is for these modules not just to provide a consistent interface to outsiders, but to guarantee the correctness of the implementation! Isn't that the whole point of types? If we can't do that, why are we using types?
Ok, so in Coq I *can* get the properties I've been wanting out of SML's module system. For instance take the following implementation of the Monad signature:
This signature describes something much like the monad that is given by the type-class in haskell. I neglected some stuff like implementing join from bind etc, but we can safely ignore that for now. The point is that users of the MONAD signature can't just fake a monad by supplying an implementation that is nominally the same. i.e. In order to implement this MONAD you actually have to have the right signature for ">>=" *AND* you have to satisfy the monad laws. So what does an implementation look like? Here is an example:
Ok, That took me about an hour to write. I'm not really that good at using Coq, so presumably you could do this more elegantly and in less time. In any case it would be nice if the proofs could be automated a bit more. That aside this is a *much* better situation than we have in SML and Haskell. We have provided a monad that is guaranteed to actually be one!
I'm of the growing opinion that software that is forced to meet specifications will end up being less trouble in the end than the current state of free-wheeling wild-west style implementation.
Coq gives a civilized alternative to the current free-for-all. Coq can help us make good on the promise that "well typed programs can't go wrong".
The gist of the problem is this. Although you can define type-classes and modules such that external users of these modules/type-classes see a uniform interface, consistency is left as an exercise for the implementer. This is not really acceptable in my view. When you are writing software, often times *you* are the implementer. What you really want is for these modules not just to provide a consistent interface to outsiders, but to guarantee the correctness of the implementation! Isn't that the whole point of types? If we can't do that, why are we using types?
Ok, so in Coq I *can* get the properties I've been wanting out of SML's module system. For instance take the following implementation of the Monad signature:
Module Type MONAD. Set Implicit Arguments. Parameter M : forall (A : Type), Type. Parameter bind : forall (A B : Type), M A -> (A -> M B) -> M B. Parameter ret : forall (A : Type), A -> M A. Infix ">>=" := bind (at level 20, left associativity) : monad_scope. Open Scope monad_scope. Axiom left_unit : forall (A B : Type) (f : A -> M B) (a : A), (ret a) >>= f = f a. Axiom right_unit : forall (A B : Type) (m : M A), m >>= (fun a : A => ret a) = m. Axiom bind_assoc : forall (A B C : Type) (m : M A) (f : A -> M B) (g : B -> M C) (x : B), (m >>= f) >>= g = m >>= (fun x => (f x) >>= g). End MONAD.
This signature describes something much like the monad that is given by the type-class in haskell. I neglected some stuff like implementing join from bind etc, but we can safely ignore that for now. The point is that users of the MONAD signature can't just fake a monad by supplying an implementation that is nominally the same. i.e. In order to implement this MONAD you actually have to have the right signature for ">>=" *AND* you have to satisfy the monad laws. So what does an implementation look like? Here is an example:
Module ListMonad < : MONAD. Require Import List. Set Implicit Arguments. Definition M := list. Fixpoint bind (A : Type) (B : Type) (l : M A) (f : A -> M B) {struct l} : M B := match l with | nil => nil | h::t => (f h)++(bind t f) end. Infix ">>=" := bind (at level 20, left associativity) : monad_scope. Open Scope monad_scope. Definition ret (A : Type) := fun a : A => a::nil. Lemma left_unit : forall (A B : Type) (f : A -> M B) (a : A), (ret a) >>= f = f a. Proof. intros. simpl. rewrite app_nil_end. reflexivity. Defined. Lemma right_unit : forall (A B : Type) (m : M A), m >>= (fun a : A => ret a) = m. Proof. simple induction m. simpl. reflexivity. intros. simpl. cut (bind l (fun a0 : A => ret a0) = l). intros. rewrite H0. reflexivity. exact H. Defined. Lemma bind_assoc : forall (A B C : Type) (m : M A) (f : A -> M B) (g : B -> M C) (x : B), (m >>= f) >>= g = m >>= (fun x => (f x) >>= g). Proof. simple induction m. intros. simpl. reflexivity. intros. simpl. cut (l >>= f >>= g = l >>= (fun x0 : A => f x0 >>= g)). intros. rewrite < - H0. induction (f a). simpl. reflexivity. simpl. rewrite IHm0. rewrite app_ass. reflexivity. apply H. exact x. Defined. End ListMonad. (* Example *) Import ListMonad. Require Import Peano. Require Import List. Fixpoint downfrom (n : nat) {struct n} : (list nat) := match n with | 0 => n::nil | S m => n::(downfrom m) end. Eval compute in (1::2::3::4::nil) >>= downfrom. = 1 :: 0 :: 2 :: 1 :: 0 :: 3 :: 2 :: 1 :: 0 :: 4 :: 3 :: 2 :: 1 :: 0 :: nil : M nat
Ok, That took me about an hour to write. I'm not really that good at using Coq, so presumably you could do this more elegantly and in less time. In any case it would be nice if the proofs could be automated a bit more. That aside this is a *much* better situation than we have in SML and Haskell. We have provided a monad that is guaranteed to actually be one!
I'm of the growing opinion that software that is forced to meet specifications will end up being less trouble in the end than the current state of free-wheeling wild-west style implementation.
Coq gives a civilized alternative to the current free-for-all. Coq can help us make good on the promise that "well typed programs can't go wrong".
Comments
Post a Comment