Aaron Mansheim on 24 Aug 2012 14:48:11 -0700 |
[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]
this past talk about continuations, and the case against call/cc |
We discussed a data structure in which we hold parsed expressions of the lambda calculus such as (λ s.λ z.s z). We discussed a similar but not identical data structure in which we hold expressions of the lambda calculus in which we have evaluated some but not all subexpressions. We discussed an algorithm to convert one representation to the other.That algorithm is a recursive post-order traversal of the _expression_ tree. I showed how we might implement that algorithm in the continuation-passing style, following the example of other subroutines in SmallStepInterpreter.scala. But rather than show that continuation-passing implementation in Scala, I showed it in the language of the Coq proof assistant, in order to introduce the topics of the following note.
The algorithms that we saw in Coq built their operations on booleans, natural numbers (that is, non-negative integers), and user-defined types from two key features of Coq.The first feature is typed lambda abstraction. For example the function in Coq that simply applies f to any member of type T is (fun (x : T) => f x). The second feature is universal quantification. For example, the type in Coq of that last function (fun (x : T) => f x)) is (forall x : T, f x).We were able to define each of our types by providing names and types for all of their constructors.For example, the boolean type has constant (that is, unparametrized) constructors "true" and "false". The natural number type "nat" has a constant constructor O to represent zero and a constructor S with parameter of type "nat" representing the successor of any natural number.The proven statements that we saw in Coq were called "theorems". Their proofs were written using "tactics".A tactic in Coq may be used to change a goal into one or more subgoals that, if all separately satisfied, are sufficient to achieve the original goal. A tactic in Coq may be used to change one or more facts in evidence into a consequent fact. Each goal or fact is a logical proposition, for example (forall x : T, f x). Through the Curry-Howard correspondence, each proposition is also a type, for example as (forall x : T, f x) is the type of (fun (x : T) => f x). The tactics for each of our completed proofs constructed a function whose outputs are terms having the conclusion types, and whose input terms have the universally quantified types and premise types.The verification of the proofs did not rely directly on the validity of the tactics themselves. Rather, the programming language's type checker took the term that the tactics produced and checked that its type corresponded to the goal proposition.For example, we had a type named eq with a lone constructor refl_equal of type (forall (X : Type), X -> X -> eq). We had an infix notation using the equals sign "=" for eq and Coq would infer X as the type of values left and right of the equals sign. That is, for any term x in type X, the term (refl_equal X x x) had type (eq) and our notation permitted us to write (refl_equal X x x) as (x = x). In order to prove (forall (x : bool), x = x), we needed only to construct the term (fun (x : bool) => refl_equal bool x x), which is the same as (fun (x : bool) => x = x). There is no single correct sequence of tactics that achieves a particular proof. For example, we could write our proof using the "apply" tactic as "apply (fun (x : bool) => x = x)." Or we could just as well write our proof using another sequence of tactics that achieves the same result, for example "intros x. apply (refl_equal bool x x)." We saw that if we use the name of a theorem and ask Coq to print that theorem, then Coq will give us a peek behind the curtain of the Curry-Howard correspondence. Specifically it will print a single relatively complex function between types that itself has the type corresponding to the theorem as logical proposition. This is true even though our tactics usually seem to express many separate and rather intuitive steps of reasoning about propositions, without making us think about constructing one big abstruse function between types.
Hi all, I thought that since we are all continuation junkies here, we might find this interesting: http://okmij.org/ftp/continuations/against-callcc.html The article argues that call/cc is a bad core feature for a language (if you are designing a language), and claims that it is even worse that goto. In any case, it is an interesting read. Yours, Trevor